Shrinkwrap vlists in table-like CSS. (#768)

TeX and CSS treat line heights in fundamentally different ways. In
TeX, every character is treated as a box of its precise height and
depth; the line height (\baselineskip) applies after characters have
been assembled into lines. In CSS, in contrast, every character
creates a "line box" corresponding to the accompanying font. When
characters of different fonts and sizes are placed into the same
span, the resulting line box contains the line boxes of all children.

This is unfortunate because, for example, we want `\frac{1}{2}` to
behave in vertical spacing contexts like it is exactly as tall and
deep as the visible fraction (which is the TeX behavior). Given CSS
constraints, though, in most contexts the fraction has extra vertical
space: the line boxes for the numerator and denominator create
padding. For small boxes, this isn't so bad. To really see the
problem put a tall rule in the denominator of a fraction, or check
out the VerticalSpacing screenshotter test, which has way more space
than it should.

Solving this problem in CSS is difficult. There is no easy way to get
rid of the extra line boxes.

But there is *a* way, namely tables. A table-cell with vertical-align
top, bottom, or middle is ignored for the purposes of line height
calculation.

So in this commit, makeVList puts its contents into a
vertical-align:bottom table-cell (to clear unwanted line boxes), with
an extra row used to represent depth.

Many Chrome screenshotter tests change. This is because Chrome rounds
table dimensions to integral numbers of pixels, while it uses
sub-pixel positioning for non-table displayed tabs. That makes many
vlists a fraction of a pixel wider than they used to be.
This commit is contained in:
Eddie Kohler
2017-07-30 11:13:55 -04:00
committed by Kevin Barabash
parent 32c7fc47d1
commit 2da06d541e
74 changed files with 81 additions and 70 deletions

View File

@@ -233,22 +233,6 @@ const makeFragment = function(children) {
return fragment;
};
/**
* Makes an element placed in each of the vlist elements to ensure that each
* element has the same max font size. To do this, we create a zero-width space
* with the correct font size.
*/
const makeFontSizer = function(options, fontSize) {
const fontSizeInner = makeSpan([], [new domTree.symbolNode("\u200b")]);
fontSizeInner.style.fontSize = fontSize + "em";
const fontSizer = makeSpan(
["fontsize-ensurer"].concat(options.baseSizingClasses()),
[fontSizeInner]);
return fontSizer;
};
/**
* Makes a vertical list by stacking elements and kerns on top of each other.
* Allows for many different ways of specifying the positioning method.
@@ -334,17 +318,28 @@ const makeVList = function(children, positionType, positionData, options) {
depth = 0;
}
// Make the fontSizer
let maxFontSize = 0;
// Create a strut that is taller than any list item. The strut is added to
// each item, where it will determine the item's baseline. Since it has
// `overflow:hidden`, the strut's top edge will sit on the item's line box's
// top edge and the strut's bottom edge will sit on the item's baseline,
// with no additional line-height spacing. This allows the item baseline to
// be positioned precisely without worrying about font ascent and
// line-height.
let pstrutSize = 0;
for (i = 0; i < children.length; i++) {
if (children[i].type === "elem") {
maxFontSize = Math.max(maxFontSize, children[i].elem.maxFontSize);
const child = children[i].elem;
pstrutSize = Math.max(pstrutSize, child.maxFontSize, child.height);
}
}
const fontSizer = makeFontSizer(options, maxFontSize);
pstrutSize += 2;
const pstrut = makeSpan(["pstrut"], []);
pstrut.style.height = pstrutSize + "em";
// Create a new list of actual children at the correct offsets
const realChildren = [];
let minPos = depth;
let maxPos = depth;
currPos = depth;
for (i = 0; i < children.length; i++) {
if (children[i].type === "kern") {
@@ -352,13 +347,8 @@ const makeVList = function(children, positionType, positionData, options) {
} else {
const child = children[i].elem;
const shift = -child.depth - currPos;
currPos += child.height + child.depth;
const childWrap = makeSpan([], [fontSizer, child]);
childWrap.height -= shift;
childWrap.depth += shift;
childWrap.style.top = shift + "em";
const childWrap = makeSpan([], [pstrut, child]);
childWrap.style.top = (-pstrutSize - currPos - child.depth) + "em";
if (children[i].marginLeft) {
childWrap.style.marginLeft = children[i].marginLeft;
}
@@ -367,21 +357,38 @@ const makeVList = function(children, positionType, positionData, options) {
}
realChildren.push(childWrap);
currPos += child.height + child.depth;
}
minPos = Math.min(minPos, currPos);
maxPos = Math.max(maxPos, currPos);
}
// Add in an element at the end with no offset to fix the calculation of
// baselines in some browsers (namely IE, sometimes safari)
const baselineFix = makeSpan(
["baseline-fix"], [fontSizer, new domTree.symbolNode("\u200b")]);
realChildren.push(baselineFix);
// The vlist contents go in a table-cell with `vertical-align:bottom`.
// This cell's bottom edge will determine the containing table's baseline
// without overly expanding the containing line-box.
const vlist = makeSpan(["vlist"], realChildren);
// Fix the final height and depth, in case there were kerns at the ends
// since the makeSpan calculation won't take that in to account.
vlist.height = Math.max(currPos, vlist.height);
vlist.depth = Math.max(-depth, vlist.depth);
return vlist;
vlist.style.height = maxPos + "em";
// A second row is used if necessary to represent the vlist's depth.
let rows;
if (minPos < 0) {
const depthStrut = makeSpan(["vlist"], []);
depthStrut.style.height = -minPos + "em";
// Safari wants the first row to have inline content; otherwise it
// puts the bottom of the *second* row on the baseline.
const topStrut = makeSpan(["vlist-s"], [new domTree.symbolNode("\u200b")]);
rows = [makeSpan(["vlist-r"], [vlist, topStrut]),
makeSpan(["vlist-r"], [depthStrut])];
} else {
rows = [makeSpan(["vlist-r"], [vlist])];
}
const vtable = makeSpan(["vlist-t"], rows);
vtable.height = maxPos;
vtable.depth = -minPos;
return vtable;
};
// A map of spacing functions to their attributes, like size and corresponding

View File

@@ -1606,16 +1606,6 @@ groupTypes.enclose = function(group, options) {
{type: "elem", elem: img, shift: imgShift},
], "individualShift", null, options);
if (img.height > vlist.maxFontSize) {
// Correct for an issue in makeVList. It placed the image top at
// the top of the line box created by a 1 em maxFontSize.
vlist.children[1].style.top = -(inner.height + pad - 0.9 / scale)
+ "em";
// The 0.9 in the previous line is there because the KaTeX fonts
// have an ascent = 0.9 em. We're setting the top of the image
// relative to the top of that line box.
}
if (/cancel/.test(label)) {
// cancel does not create horiz space for its line extension.
// That is, not when adjacent to a mord.
@@ -1633,12 +1623,14 @@ groupTypes.xArrow = function(group, options) {
let newOptions = options.havingStyle(style.sup());
const upperGroup = buildGroup(group.value.body, newOptions, options);
upperGroup.classes.push("x-arrow-pad");
let lowerGroup;
if (group.value.below) {
// Build the lower group
newOptions = options.havingStyle(style.sub());
lowerGroup = buildGroup(group.value.below, newOptions, options);
lowerGroup.classes.push("x-arrow-pad");
}
const arrowBody = stretchy.svgSpan(group, options);
@@ -1665,10 +1657,7 @@ groupTypes.xArrow = function(group, options) {
], "individualShift", null, options);
}
const node = makeSpan(["mrel", "x-arrow"], [vlist], options);
node.depth = node.depth;
node.height = node.height;
return node;
return makeSpan(["mrel", "x-arrow"], [vlist], options);
};
groupTypes.phantom = function(group, options) {

View File

@@ -232,8 +232,25 @@
& + .mop.mtight { margin-left: @thinspace; }
}
.vlist-t {
display: inline-table;
table-layout: fixed;
}
.vlist-r {
display: table-row;
}
.vlist-s {
display: table-cell;
vertical-align: bottom;
font-size: 0.05em;
}
.vlist {
display: inline-block;
display: table-cell;
vertical-align: bottom;
position: relative;
> span {
display: block;
@@ -243,11 +260,11 @@
> span {
display: inline-block;
}
}
.baseline-fix {
display: inline-table;
table-layout: fixed;
> .pstrut {
overflow: hidden;
width: 0;
}
}
}
@@ -503,13 +520,13 @@
}
.op-limits {
> .vlist > span {
> .vlist-t {
text-align: center;
}
}
.accent {
> .vlist > span {
> .vlist-t {
text-align: center;
}
@@ -536,15 +553,15 @@
display: inline-block;
}
.col-align-c > .vlist {
.col-align-c > .vlist-t {
text-align: center;
}
.col-align-l > .vlist {
.col-align-l > .vlist-t {
text-align: left;
}
.col-align-r > .vlist {
.col-align-r > .vlist-t {
text-align: right;
}
}
@@ -564,16 +581,14 @@
}
// Lengthen the extensible arrows via padding.
.x-arrow > span > span {
text-align: center;
> span > .mord {
padding: 0 0.5em 0 0.5em;
}
.x-arrow-pad {
padding: 0 0.5em;
}
.mover > span > span,
.munder > span > span {
text-align: center; // above and below a horizontal brace
.x-arrow,
.mover,
.munder {
text-align: center;
}
.boxpad {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB