Fix font sizing issues

Summary:
In LaTeX, large delimiters are the same font size as they are at a normal size,
regardless of the actual size. This means that we need to scale up the font size
in the inner nodes, which is annoying because we run into the same problem we
had with \Huge, etc in those nodes. Thus, this fixes both problems at once.

The problem was that when we used our baseline-align-hack and then increased the
font size inside of one of the middle (display: block and height: 0) nodes, the
node with the increased font size would shift downards (misaligning its
baseline). To fix this, we add a method for calculating the maximum font size
used in each of the nodes, and adding a small node with this font size to each
of the other nodes (including the fix-ie node). This shifts all of the nodes
down the same amount, and gets their baselines aligned.

Test Plan:
 - Do dumb things by putting \Huge and \big in places they shouldn't be, and
   make sure they behave responsibly
 - Do the same thing in IE 8, 9, 10, 11, Safari, Firefox, and make sure they all
   behave the same (to some approximation)
 - Make sure the new huxley image looks good, and the images that changed don't
   have significant changes

Reviewers: alpert

Reviewed By: alpert

Differential Revision: http://phabricator.khanacademy.org/D12684
This commit is contained in:
Emily Eisenberg 2014-08-27 01:12:15 -07:00
parent fa3df2db6f
commit 04f13b9be8
11 changed files with 146 additions and 71 deletions

View File

@ -1,14 +1,8 @@
function Options(style, size, color, deep, parentStyle, parentSize) { function Options(style, size, color, parentStyle, parentSize) {
this.style = style; this.style = style;
this.color = color; this.color = color;
this.size = size; this.size = size;
// TODO(emily): Get rid of deep when we can actually use sizing everywhere
if (deep === undefined) {
deep = false;
}
this.deep = deep;
if (parentStyle === undefined) { if (parentStyle === undefined) {
parentStyle = style; parentStyle = style;
} }
@ -21,28 +15,20 @@ function Options(style, size, color, deep, parentStyle, parentSize) {
} }
Options.prototype.withStyle = function(style) { Options.prototype.withStyle = function(style) {
return new Options(style, this.size, this.color, this.deep, return new Options(style, this.size, this.color, this.style, this.size);
this.style, this.size);
}; };
Options.prototype.withSize = function(size) { Options.prototype.withSize = function(size) {
return new Options(this.style, size, this.color, this.deep, return new Options(this.style, size, this.color, this.style, this.size);
this.style, this.size);
}; };
Options.prototype.withColor = function(color) { Options.prototype.withColor = function(color) {
return new Options(this.style, this.size, color, this.deep, return new Options(this.style, this.size, color, this.style, this.size);
this.style, this.size);
};
Options.prototype.deepen = function() {
return new Options(this.style, this.size, this.color, true,
this.parentStyle, this.parentSize);
}; };
Options.prototype.reset = function() { Options.prototype.reset = function() {
return new Options(this.style, this.size, this.color, this.deep, return new Options(
this.style, this.size); this.style, this.size, this.color, this.style, this.size);
}; };
var colorMap = { var colorMap = {

View File

@ -21,6 +21,7 @@ var buildExpression = function(expression, options, prev) {
var makeSpan = function(classes, children, color) { var makeSpan = function(classes, children, color) {
var height = 0; var height = 0;
var depth = 0; var depth = 0;
var maxFontSize = 0;
if (children) { if (children) {
for (var i = 0; i < children.length; i++) { for (var i = 0; i < children.length; i++) {
@ -30,10 +31,14 @@ var makeSpan = function(classes, children, color) {
if (children[i].depth > depth) { if (children[i].depth > depth) {
depth = children[i].depth; depth = children[i].depth;
} }
if (children[i].maxFontSize > maxFontSize) {
maxFontSize = children[i].maxFontSize;
}
} }
} }
var span = new domTree.span(classes, children, height, depth); var span = new domTree.span(
classes, children, height, depth, maxFontSize);
if (color) { if (color) {
span.style.color = color; span.style.color = color;
@ -42,6 +47,17 @@ var makeSpan = function(classes, children, color) {
return span; return span;
}; };
var makeFontSizer = function(options, fontSize) {
var fontSizeInner = makeSpan([], [new domTree.textNode("\u200b")]);
fontSizeInner.style.fontSize = (fontSize / options.style.sizeMultiplier) + "em";
var fontSizer = makeSpan(
["fontsize-ensurer", "reset-" + options.size, "size5"],
[fontSizeInner]);
return fontSizer;
};
var groupToType = { var groupToType = {
mathord: "mord", mathord: "mord",
textord: "mord", textord: "mord",
@ -142,7 +158,7 @@ var groupTypes = {
text: function(group, options, prev) { text: function(group, options, prev) {
return makeSpan(["text mord", options.style.cls()], return makeSpan(["text mord", options.style.cls()],
[buildGroup(group.value, options.deepen())] [buildGroup(group.value, options.reset())]
); );
}, },
@ -151,18 +167,16 @@ var groupTypes = {
if (group.value.sup) { if (group.value.sup) {
var sup = buildGroup(group.value.sup, var sup = buildGroup(group.value.sup,
options.withStyle(options.style.sup()).deepen()); options.withStyle(options.style.sup()));
var supmid = makeSpan( var supmid = makeSpan(
[options.style.reset(), options.style.sup().cls()], [sup]); [options.style.reset(), options.style.sup().cls()], [sup]);
var supwrap = makeSpan(["msup", options.style.reset()], [supmid]);
} }
if (group.value.sub) { if (group.value.sub) {
var sub = buildGroup(group.value.sub, var sub = buildGroup(group.value.sub,
options.withStyle(options.style.sub()).deepen()); options.withStyle(options.style.sub()));
var submid = makeSpan( var submid = makeSpan(
[options.style.reset(), options.style.sub().cls()], [sub]); [options.style.reset(), options.style.sub().cls()], [sub]);
var subwrap = makeSpan(["msub"], [submid]);
} }
if (isCharacterBox(group.value.base)) { if (isCharacterBox(group.value.base)) {
@ -183,9 +197,11 @@ var groupTypes = {
} }
var supsub; var supsub;
var fixIE = makeSpan(["fix-ie"], [new domTree.textNode("\u00a0")]);
if (!group.value.sup) { if (!group.value.sup) {
var fontSizer = makeFontSizer(options, submid.maxFontSize);
var subwrap = makeSpan(["msub"], [fontSizer, submid]);
v = Math.max(v, fontMetrics.metrics.sub1, v = Math.max(v, fontMetrics.metrics.sub1,
sub.height - 0.8 * fontMetrics.metrics.xHeight); sub.height - 0.8 * fontMetrics.metrics.xHeight);
@ -194,8 +210,13 @@ var groupTypes = {
subwrap.depth = subwrap.depth + v; subwrap.depth = subwrap.depth + v;
subwrap.height = 0; subwrap.height = 0;
var fixIE = makeSpan(["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
supsub = makeSpan(["msupsub"], [subwrap, fixIE]); supsub = makeSpan(["msupsub"], [subwrap, fixIE]);
} else if (!group.value.sub) { } else if (!group.value.sub) {
var fontSizer = makeFontSizer(options, supmid.maxFontSize);
var supwrap = makeSpan(["msup"], [fontSizer, supmid]);
u = Math.max(u, p, u = Math.max(u, p,
sup.depth + 0.25 * fontMetrics.metrics.xHeight); sup.depth + 0.25 * fontMetrics.metrics.xHeight);
@ -204,8 +225,15 @@ var groupTypes = {
supwrap.height = supwrap.height + u; supwrap.height = supwrap.height + u;
supwrap.depth = 0; supwrap.depth = 0;
var fixIE = makeSpan(["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
supsub = makeSpan(["msupsub"], [supwrap, fixIE]); supsub = makeSpan(["msupsub"], [supwrap, fixIE]);
} else { } else {
var fontSizer = makeFontSizer(options,
Math.max(submid.maxFontSize, supmid.maxFontSize));
var subwrap = makeSpan(["msub"], [fontSizer, submid]);
var supwrap = makeSpan(["msup"], [fontSizer, supmid]);
u = Math.max(u, p, u = Math.max(u, p,
sup.depth + 0.25 * fontMetrics.metrics.xHeight); sup.depth + 0.25 * fontMetrics.metrics.xHeight);
v = Math.max(v, fontMetrics.metrics.sub2); v = Math.max(v, fontMetrics.metrics.sub2);
@ -230,6 +258,8 @@ var groupTypes = {
subwrap.height = 0; subwrap.height = 0;
subwrap.depth = subwrap.depth + v; subwrap.depth = subwrap.depth + v;
var fixIE = makeSpan(["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
supsub = makeSpan(["msupsub"], [supwrap, subwrap, fixIE]); supsub = makeSpan(["msupsub"], [supwrap, subwrap, fixIE]);
} }
@ -263,15 +293,18 @@ var groupTypes = {
var nstyle = fstyle.fracNum(); var nstyle = fstyle.fracNum();
var dstyle = fstyle.fracDen(); var dstyle = fstyle.fracDen();
var numer = buildGroup(group.value.numer, options.withStyle(nstyle).deepen()); var numer = buildGroup(group.value.numer, options.withStyle(nstyle));
var numernumer = makeSpan([fstyle.reset(), nstyle.cls()], [numer]); var numernumer = makeSpan([fstyle.reset(), nstyle.cls()], [numer]);
var numerrow = makeSpan(["mfracnum"], [numernumer]);
var mid = makeSpan(["mfracmid"], [makeSpan()]); var denom = buildGroup(group.value.denom, options.withStyle(dstyle));
var denom = buildGroup(group.value.denom, options.withStyle(dstyle).deepen());
var denomdenom = makeSpan([fstyle.reset(), dstyle.cls()], [denom]) var denomdenom = makeSpan([fstyle.reset(), dstyle.cls()], [denom])
var denomrow = makeSpan(["mfracden"], [denomdenom]);
var fontSizer = makeFontSizer(options,
Math.max(numer.maxFontSize, denom.maxFontSize));
var numerrow = makeSpan(["mfracnum"], [fontSizer, numernumer]);
var mid = makeSpan(["mfracmid"], [fontSizer, makeSpan(["line"])]);
var denomrow = makeSpan(["mfracden"], [fontSizer, denomdenom]);
var theta = fontMetrics.metrics.defaultRuleThickness; var theta = fontMetrics.metrics.defaultRuleThickness;
@ -306,7 +339,8 @@ var groupTypes = {
denomrow.height = 0; denomrow.height = 0;
denomrow.depth = denomrow.depth + v; denomrow.depth = denomrow.depth + v;
var fixIE = makeSpan(["fix-ie"], [new domTree.textNode("\u00a0")]); var fixIE = makeSpan(["fix-ie"], [
fontSizer, new domTree.textNode("\u00a0")]);
var frac = makeSpan([], [numerrow, mid, denomrow, fixIE]); var frac = makeSpan([], [numerrow, mid, denomrow, fixIE]);
@ -366,14 +400,14 @@ var groupTypes = {
llap: function(group, options, prev) { llap: function(group, options, prev) {
var inner = makeSpan( var inner = makeSpan(
["inner"], [buildGroup(group.value, options.deepen())]); ["inner"], [buildGroup(group.value, options.reset())]);
var fix = makeSpan(["fix"], []); var fix = makeSpan(["fix"], []);
return makeSpan(["llap", options.style.cls()], [inner, fix]); return makeSpan(["llap", options.style.cls()], [inner, fix]);
}, },
rlap: function(group, options, prev) { rlap: function(group, options, prev) {
var inner = makeSpan( var inner = makeSpan(
["inner"], [buildGroup(group.value, options.deepen())]); ["inner"], [buildGroup(group.value, options.reset())]);
var fix = makeSpan(["fix"], []); var fix = makeSpan(["fix"], []);
return makeSpan(["rlap", options.style.cls()], [inner, fix]); return makeSpan(["rlap", options.style.cls()], [inner, fix]);
}, },
@ -422,14 +456,18 @@ var groupTypes = {
overline: function(group, options, prev) { overline: function(group, options, prev) {
var innerGroup = buildGroup(group.value.result, var innerGroup = buildGroup(group.value.result,
options.withStyle(options.style.cramp()).deepen()); options.withStyle(options.style.cramp()));
var fontSizer = makeFontSizer(options, innerGroup.maxFontSize);
// The theta variable in the TeXbook // The theta variable in the TeXbook
var lineWidth = fontMetrics.metrics.defaultRuleThickness; var lineWidth = fontMetrics.metrics.defaultRuleThickness;
var line = makeSpan(["overline-line"], [makeSpan([])]); var line = makeSpan(
var inner = makeSpan(["overline-inner"], [innerGroup]); ["overline-line"], [fontSizer, makeSpan(["line"])]);
var fixIE = makeSpan(["fix-ie"], []); var inner = makeSpan(["overline-inner"], [fontSizer, innerGroup]);
var fixIE = makeSpan(
["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
line.style.top = (-inner.height - 3 * lineWidth) + "em"; line.style.top = (-inner.height - 3 * lineWidth) + "em";
// The line is supposed to have 1 extra line width above it in height // The line is supposed to have 1 extra line width above it in height
@ -445,10 +483,27 @@ var groupTypes = {
var inner = buildGroup(group.value.value, var inner = buildGroup(group.value.value,
options.withSize(group.value.size), prev); options.withSize(group.value.size), prev);
return makeSpan( var span = makeSpan([getTypeOfGroup(group.value.value)],
["sizing", "reset-" + options.size, group.value.size, [makeSpan(["sizing", "reset-" + options.size, group.value.size],
getTypeOfGroup(group.value.value)], [inner])]);
[inner]);
var sizeToFontSize = {
"size1": 0.5,
"size2": 0.7,
"size3": 0.8,
"size4": 0.9,
"size5": 1.0,
"size6": 1.2,
"size7": 1.44,
"size8": 1.73,
"size9": 2.07,
"size10": 2.49
};
var fontSize = sizeToFontSize[group.value.size];
span.maxFontSize = fontSize * options.style.sizeMultiplier;
return span;
}, },
delimsizing: function(group, options, prev) { delimsizing: function(group, options, prev) {
@ -518,9 +573,21 @@ var groupTypes = {
var inner = mathrmSize( var inner = mathrmSize(
original, group.value.size, group.mode); original, group.value.size, group.mode);
return makeSpan( var node = makeSpan(
[options.style.reset(), Style.TEXT.cls(),
groupToType[group.value.type]],
[makeSpan(
["delimsizing", size, groupToType[group.value.type]], ["delimsizing", size, groupToType[group.value.type]],
[inner], options.getColor()); [inner], options.getColor())]);
var multiplier = Style.TEXT.sizeMultiplier /
options.style.sizeMultiplier;
node.height *= multiplier;
node.depth *= multiplier;
node.maxFontSize = 1.0;
return node;
} else if (utils.contains(stackDelimiters, original)) { } else if (utils.contains(stackDelimiters, original)) {
// These delimiters can be created by stacking other delimiters on // These delimiters can be created by stacking other delimiters on
// top of each other to create the correct size // top of each other to create the correct size
@ -602,9 +669,20 @@ var groupTypes = {
var fixIE = makeSpan(["fix-ie"], [new domTree.textNode("\u00a0")]); var fixIE = makeSpan(["fix-ie"], [new domTree.textNode("\u00a0")]);
inners.push(fixIE); inners.push(fixIE);
return makeSpan( var node = makeSpan(
["delimsizing", "mult", groupToType[group.value.type]], [options.style.reset(), Style.TEXT.cls(),
inners, options.getColor()); groupToType[group.value.type]],
[makeSpan(["delimsizing", "mult"],
inners, options.getColor())]);
var multiplier = Style.TEXT.sizeMultiplier /
options.style.sizeMultiplier;
node.height *= multiplier;
node.depth *= multiplier;
node.maxFontSize = 1.0;
return node;
} else { } else {
throw new ParseError("Illegal delimiter: '" + original + "'"); throw new ParseError("Illegal delimiter: '" + original + "'");
} }
@ -644,11 +722,6 @@ var buildGroup = function(group, options, prev) {
var multiplier = sizingMultiplier[options.size] / var multiplier = sizingMultiplier[options.size] /
sizingMultiplier[options.parentSize]; sizingMultiplier[options.parentSize];
if (options.deep) {
throw new ParseError(
"Can't use sizing outside of the root node");
}
groupNode.height *= multiplier; groupNode.height *= multiplier;
groupNode.depth *= multiplier; groupNode.depth *= multiplier;
} }

View File

@ -3,11 +3,12 @@
// function. They are useful for both storing extra properties on the nodes, as // function. They are useful for both storing extra properties on the nodes, as
// well as providing a way to easily work with the DOM. // well as providing a way to easily work with the DOM.
function span(classes, children, height, depth, style) { function span(classes, children, height, depth, maxFontSize, style) {
this.classes = classes || []; this.classes = classes || [];
this.children = children || []; this.children = children || [];
this.height = height || 0; this.height = height || 0;
this.depth = depth || 0; this.depth = depth || 0;
this.maxFontSize = maxFontSize || 0;
this.style = style || {}; this.style = style || {};
} }

View File

@ -34,6 +34,11 @@ big parens
font-family: KaTeX_AMS; font-family: KaTeX_AMS;
} }
.fix-ie {
display: inline-table;
table-layout: fixed;
}
@thinspace: 0.16667em; @thinspace: 0.16667em;
@mediumspace: 0.22222em; @mediumspace: 0.22222em;
@thickspace: 0.27778em; @thickspace: 0.27778em;
@ -188,10 +193,6 @@ big parens
.baseline-align-hack-inner; .baseline-align-hack-inner;
} }
} }
.fix-ie {
display: inline-block;
}
} }
.mfrac { .mfrac {
@ -210,11 +211,7 @@ big parens
} }
} }
.fix-ie { .mfracmid > .line {
display: inline-block;
}
.mfracmid > span {
width: 100%; width: 100%;
&:before { &:before {
@ -323,11 +320,7 @@ big parens
} }
} }
> .fix-ie { > .overline-line > .line {
display: inline-block;
}
> .overline-line > span {
width: 100%; width: 100%;
&:before { &:before {
@ -347,7 +340,7 @@ big parens
} }
} }
.sizing { .sizing, .fontsize-ensurer {
display: inline-block; display: inline-block;
@size1: 0.5; @size1: 0.5;

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -105,5 +105,17 @@
"name": "Overline", "name": "Overline",
"screenSize": [1024, 768], "screenSize": [1024, 768],
"url": "http://localhost:7936/test/huxley/test.html?m=\\overline{x}\\overline{x}\\overline{x^{x^{x^x}}} \\blue\\overline{y}" "url": "http://localhost:7936/test/huxley/test.html?m=\\overline{x}\\overline{x}\\overline{x^{x^{x^x}}} \\blue\\overline{y}"
},
{
"name": "DeepFontSizing",
"screenSize": [1024, 768],
"url": "http://localhost:7936/test/huxley/test.html?m=a^{\\big| x^{\\big(}}_{\\Big\\uparrow} + i^{i^{\\Huge x}_y}_{\\Huge z} + \\dfrac{\\Huge x}{y}"
},
{
"name": "VerticalSpacing",
"screenSize": [1024, 768],
"url": "http://localhost:7936/test/huxley/test.html?pre=potato<br>&post=<br>moo&m=x^{\\Huge y}z"
} }
] ]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

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