Change buildCommon.makeVList params to struct for better type-safety. (#940)

* Change buildCommon.makeVList params to struct for better type-safety.

This is towards #939.

The expected structure of the makeVList params depends on the `positionType`
parameter. To allow this to be strictly-typed using flow, this PR combines the
co-related parameters into a single struct.

* Add type for makeVList param in comments for documentation.
This commit is contained in:
Ashish Myles
2017-10-17 07:43:10 -04:00
committed by Kevin Barabash
parent b4c5dfaf20
commit 305a35c3a5
5 changed files with 242 additions and 157 deletions

View File

@@ -247,46 +247,47 @@ const makeFragment = function(children) {
return fragment; return fragment;
}; };
// TODO(#939): Uncomment and use VListParam as the type of makeVList's first param.
/*
type VListElem =
{type: "elem", elem: DomChildNode, marginLeft?: string, marginRight?: string};
type VListKern = {type: "kern", size: number};
// A list of child or kern nodes to be stacked on top of each other (i.e. the
// first element will be at the bottom, and the last at the top).
type VListChild = VListElem | VListKern;
type VListParam = {|
// Each child contains how much it should be shifted downward.
positionType: "individualShift",
children: (VListElem & {shift: number})[],
|} | {|
// "top": The positionData specifies the topmost point of the vlist (note this
// is expected to be a height, so positive values move up).
// "bottom": The positionData specifies the bottommost point of the vlist (note
// this is expected to be a depth, so positive values move down).
// "shift": The vlist will be positioned such that its baseline is positionData
// away from the baseline of the first child. Positive values move
// downwards.
positionType: "top" | "bottom" | "shift",
positionData: number,
children: VListChild[],
|} | {|
// The vlist is positioned so that its baseline is aligned with the baseline
// of the first child. This is equivalent to "shift" with positionData=0.
positionType: "firstBaseline",
children: VListChild[],
|};
*/
/** /**
* Makes a vertical list by stacking elements and kerns on top of each other. * Makes a vertical list by stacking elements and kerns on top of each other.
* Allows for many different ways of specifying the positioning method. * Allows for many different ways of specifying the positioning method.
* *
* Arguments: * See parameter documentation on the type documentation above.
* - children: A list of child or kern nodes to be stacked on top of each other
* (i.e. the first element will be at the bottom, and the last at
* the top). Element nodes are specified as
* {type: "elem", elem: node}
* while kern nodes are specified as
* {type: "kern", size: size}
* - positionType: The method by which the vlist should be positioned. Valid
* values are:
* - "individualShift": The children list only contains elem
* nodes, and each node contains an extra
* "shift" value of how much it should be
* shifted (note that shifting is always
* moving downwards). positionData is
* ignored.
* - "top": The positionData specifies the topmost point of
* the vlist (note this is expected to be a height,
* so positive values move up)
* - "bottom": The positionData specifies the bottommost point
* of the vlist (note this is expected to be a
* depth, so positive values move down
* - "shift": The vlist will be positioned such that its
* baseline is positionData away from the baseline
* of the first child. Positive values move
* downwards.
* - "firstBaseline": The vlist will be positioned such that
* its baseline is aligned with the
* baseline of the first child.
* positionData is ignored. (this is
* equivalent to "shift" with
* positionData=0)
* - positionData: Data used in different ways depending on positionType
* - options: An Options object
*
*/ */
const makeVList = function(children, positionType, positionData, options) { const makeVList = function({positionType, positionData, children}, options) {
let depth; let depth;
let currPos; let currPos;
let i; let i;

View File

@@ -397,15 +397,21 @@ groupTypes.supsub = function(group, options) {
vlistElem[0].marginLeft = -base.italic + "em"; vlistElem[0].marginLeft = -base.italic + "em";
} }
supsub = buildCommon.makeVList(vlistElem, "shift", subShift, options); supsub = buildCommon.makeVList({
positionType: "shift",
positionData: subShift,
children: vlistElem,
}, options);
} else if (!group.value.sub) { } else if (!group.value.sub) {
// Rule 18c, d // Rule 18c, d
supShift = Math.max(supShift, minSupShift, supShift = Math.max(supShift, minSupShift,
supm.depth + 0.25 * metrics.xHeight); supm.depth + 0.25 * metrics.xHeight);
supsub = buildCommon.makeVList([ supsub = buildCommon.makeVList({
{type: "elem", elem: supm, marginRight: scriptspace}, positionType: "shift",
], "shift", -supShift, options); positionData: -supShift,
children: [{type: "elem", elem: supm, marginRight: scriptspace}],
}, options);
} else { } else {
supShift = Math.max( supShift = Math.max(
supShift, minSupShift, supm.depth + 0.25 * metrics.xHeight); supShift, minSupShift, supm.depth + 0.25 * metrics.xHeight);
@@ -433,7 +439,10 @@ groupTypes.supsub = function(group, options) {
vlistElem[0].marginLeft = -base.italic + "em"; vlistElem[0].marginLeft = -base.italic + "em";
} }
supsub = buildCommon.makeVList(vlistElem, "individualShift", null, options); supsub = buildCommon.makeVList({
positionType: "individualShift",
children: vlistElem,
}, options);
} }
// We ensure to wrap the supsub vlist in a span.msupsub to reset text-align // We ensure to wrap the supsub vlist in a span.msupsub to reset text-align
@@ -510,10 +519,13 @@ groupTypes.genfrac = function(group, options) {
denomShift += 0.5 * (clearance - candidateClearance); denomShift += 0.5 * (clearance - candidateClearance);
} }
frac = buildCommon.makeVList([ frac = buildCommon.makeVList({
{type: "elem", elem: denomm, shift: denomShift}, positionType: "individualShift",
{type: "elem", elem: numerm, shift: -numShift}, children: [
], "individualShift", null, options); {type: "elem", elem: denomm, shift: denomShift},
{type: "elem", elem: numerm, shift: -numShift},
],
}, options);
} else { } else {
// Rule 15d // Rule 15d
const axisHeight = options.fontMetrics().axisHeight; const axisHeight = options.fontMetrics().axisHeight;
@@ -534,11 +546,14 @@ groupTypes.genfrac = function(group, options) {
const midShift = -(axisHeight - 0.5 * ruleWidth); const midShift = -(axisHeight - 0.5 * ruleWidth);
frac = buildCommon.makeVList([ frac = buildCommon.makeVList({
{type: "elem", elem: denomm, shift: denomShift}, positionType: "individualShift",
{type: "elem", elem: rule, shift: midShift}, children: [
{type: "elem", elem: numerm, shift: -numShift}, {type: "elem", elem: denomm, shift: denomShift},
], "individualShift", null, options); {type: "elem", elem: rule, shift: midShift},
{type: "elem", elem: numerm, shift: -numShift},
],
}, options);
} }
// Since we manually change the style sometimes (with \dfrac or \tfrac), // Since we manually change the style sometimes (with \dfrac or \tfrac),
@@ -648,9 +663,10 @@ groupTypes.smash = function(group, options) {
// makeVList applies "display: table-cell", which prevents the browser // makeVList applies "display: table-cell", which prevents the browser
// from acting on that line height. So we'll call makeVList now. // from acting on that line height. So we'll call makeVList now.
return buildCommon.makeVList([ return buildCommon.makeVList({
{type: "elem", elem: node}, positionType: "firstBaseline",
], "firstBaseline", null, options); children: [{type: "elem", elem: node}],
}, options);
}; };
groupTypes.op = function(group, options) { groupTypes.op = function(group, options) {
@@ -770,21 +786,29 @@ groupTypes.op = function(group, options) {
// that we are supposed to shift the limits by 1/2 of the slant, // that we are supposed to shift the limits by 1/2 of the slant,
// but since we are centering the limits adding a full slant of // but since we are centering the limits adding a full slant of
// margin will shift by 1/2 that. // margin will shift by 1/2 that.
finalGroup = buildCommon.makeVList([ finalGroup = buildCommon.makeVList({
{type: "kern", size: options.fontMetrics().bigOpSpacing5}, positionType: "top",
{type: "elem", elem: subm, marginLeft: -slant + "em"}, positionData: top,
{type: "kern", size: subKern}, children: [
{type: "elem", elem: base}, {type: "kern", size: options.fontMetrics().bigOpSpacing5},
], "top", top, options); {type: "elem", elem: subm, marginLeft: -slant + "em"},
{type: "kern", size: subKern},
{type: "elem", elem: base},
],
}, options);
} else if (!subGroup) { } else if (!subGroup) {
bottom = base.depth + baseShift; bottom = base.depth + baseShift;
finalGroup = buildCommon.makeVList([ finalGroup = buildCommon.makeVList({
{type: "elem", elem: base}, positionType: "bottom",
{type: "kern", size: supKern}, positionData: bottom,
{type: "elem", elem: supm, marginLeft: slant + "em"}, children: [
{type: "kern", size: options.fontMetrics().bigOpSpacing5}, {type: "elem", elem: base},
], "bottom", bottom, options); {type: "kern", size: supKern},
{type: "elem", elem: supm, marginLeft: slant + "em"},
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
],
}, options);
} else if (!supGroup && !subGroup) { } else if (!supGroup && !subGroup) {
// This case probably shouldn't occur (this would mean the // This case probably shouldn't occur (this would mean the
// supsub was sending us a group with no superscript or // supsub was sending us a group with no superscript or
@@ -796,15 +820,19 @@ groupTypes.op = function(group, options) {
subKern + subKern +
base.depth + baseShift; base.depth + baseShift;
finalGroup = buildCommon.makeVList([ finalGroup = buildCommon.makeVList({
{type: "kern", size: options.fontMetrics().bigOpSpacing5}, positionType: "bottom",
{type: "elem", elem: subm, marginLeft: -slant + "em"}, positionData: bottom,
{type: "kern", size: subKern}, children: [
{type: "elem", elem: base}, {type: "kern", size: options.fontMetrics().bigOpSpacing5},
{type: "kern", size: supKern}, {type: "elem", elem: subm, marginLeft: -slant + "em"},
{type: "elem", elem: supm, marginLeft: slant + "em"}, {type: "kern", size: subKern},
{type: "kern", size: options.fontMetrics().bigOpSpacing5}, {type: "elem", elem: base},
], "bottom", bottom, options); {type: "kern", size: supKern},
{type: "elem", elem: supm, marginLeft: slant + "em"},
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
],
}, options);
} }
return makeSpan(["mop", "op-limits"], [finalGroup], options); return makeSpan(["mop", "op-limits"], [finalGroup], options);
@@ -917,12 +945,15 @@ groupTypes.overline = function(group, options) {
const line = makeLineSpan("overline-line", options); const line = makeLineSpan("overline-line", options);
// Generate the vlist, with the appropriate kerns // Generate the vlist, with the appropriate kerns
const vlist = buildCommon.makeVList([ const vlist = buildCommon.makeVList({
{type: "elem", elem: innerGroup}, positionType: "firstBaseline",
{type: "kern", size: 3 * line.height}, children: [
{type: "elem", elem: line}, {type: "elem", elem: innerGroup},
{type: "kern", size: line.height}, {type: "kern", size: 3 * line.height},
], "firstBaseline", null, options); {type: "elem", elem: line},
{type: "kern", size: line.height},
],
}, options);
return makeSpan(["mord", "overline"], [vlist], options); return makeSpan(["mord", "overline"], [vlist], options);
}; };
@@ -936,12 +967,16 @@ groupTypes.underline = function(group, options) {
const line = makeLineSpan("underline-line", options); const line = makeLineSpan("underline-line", options);
// Generate the vlist, with the appropriate kerns // Generate the vlist, with the appropriate kerns
const vlist = buildCommon.makeVList([ const vlist = buildCommon.makeVList({
{type: "kern", size: line.height}, positionType: "top",
{type: "elem", elem: line}, positionData: innerGroup.height,
{type: "kern", size: 3 * line.height}, children: [
{type: "elem", elem: innerGroup}, {type: "kern", size: line.height},
], "top", innerGroup.height, options); {type: "elem", elem: line},
{type: "kern", size: 3 * line.height},
{type: "elem", elem: innerGroup},
],
}, options);
return makeSpan(["mord", "underline"], [vlist], options); return makeSpan(["mord", "underline"], [vlist], options);
}; };
@@ -1003,12 +1038,15 @@ groupTypes.sqrt = function(group, options) {
inner.style.paddingLeft = img.advanceWidth + "em"; inner.style.paddingLeft = img.advanceWidth + "em";
// Overlay the image and the argument. // Overlay the image and the argument.
const body = buildCommon.makeVList([ const body = buildCommon.makeVList({
{type: "elem", elem: inner}, positionType: "firstBaseline",
{type: "kern", size: -(inner.height + imgShift)}, children: [
{type: "elem", elem: img}, {type: "elem", elem: inner},
{type: "kern", size: ruleWidth}, {type: "kern", size: -(inner.height + imgShift)},
], "firstBaseline", null, options); {type: "elem", elem: img},
{type: "kern", size: ruleWidth},
],
}, options);
body.children[0].children[0].classes.push("svg-align"); body.children[0].children[0].classes.push("svg-align");
if (!group.value.index) { if (!group.value.index) {
@@ -1025,9 +1063,11 @@ groupTypes.sqrt = function(group, options) {
const toShift = 0.6 * (body.height - body.depth); const toShift = 0.6 * (body.height - body.depth);
// Build a VList with the superscript shifted up correctly // Build a VList with the superscript shifted up correctly
const rootVList = buildCommon.makeVList( const rootVList = buildCommon.makeVList({
[{type: "elem", elem: rootm}], positionType: "shift",
"shift", -toShift, options); positionData: -toShift,
children: [{type: "elem", elem: rootm}],
}, options);
// Add a class surrounding it so we can add on the appropriate // Add a class surrounding it so we can add on the appropriate
// kerning // kerning
const rootVListWrap = makeSpan(["root"], [rootVList]); const rootVListWrap = makeSpan(["root"], [rootVList]);
@@ -1246,19 +1286,25 @@ groupTypes.accent = function(group, options) {
// we shift it to the right by 1*skew. // we shift it to the right by 1*skew.
accentBody.style.marginLeft = 2 * skew + "em"; accentBody.style.marginLeft = 2 * skew + "em";
accentBody = buildCommon.makeVList([ accentBody = buildCommon.makeVList({
{type: "elem", elem: body}, positionType: "firstBaseline",
{type: "kern", size: -clearance}, children: [
{type: "elem", elem: accentBody}, {type: "elem", elem: body},
], "firstBaseline", null, options); {type: "kern", size: -clearance},
{type: "elem", elem: accentBody},
],
}, options);
} else { } else {
accentBody = stretchy.svgSpan(group, options); accentBody = stretchy.svgSpan(group, options);
accentBody = buildCommon.makeVList([ accentBody = buildCommon.makeVList({
{type: "elem", elem: body}, positionType: "firstBaseline",
{type: "elem", elem: accentBody}, children: [
], "firstBaseline", null, options); {type: "elem", elem: body},
{type: "elem", elem: accentBody},
],
}, options);
const styleSpan = accentBody.children[0].children[0].children[1]; const styleSpan = accentBody.children[0].children[0].children[1];
styleSpan.classes.push("svg-align"); // text-align: left; styleSpan.classes.push("svg-align"); // text-align: left;
@@ -1320,18 +1366,25 @@ groupTypes.horizBrace = function(group, options) {
// This first vlist contains the subject matter and the brace: equation // This first vlist contains the subject matter and the brace: equation
let vlist; let vlist;
if (group.value.isOver) { if (group.value.isOver) {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList({
{type: "elem", elem: body}, positionType: "firstBaseline",
{type: "kern", size: 0.1}, children: [
{type: "elem", elem: braceBody}, {type: "elem", elem: body},
], "firstBaseline", null, options); {type: "kern", size: 0.1},
{type: "elem", elem: braceBody},
],
}, options);
vlist.children[0].children[0].children[1].classes.push("svg-align"); vlist.children[0].children[0].children[1].classes.push("svg-align");
} else { } else {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList({
{type: "elem", elem: braceBody}, positionType: "bottom",
{type: "kern", size: 0.1}, positionData: body.depth + 0.1 + braceBody.height,
{type: "elem", elem: body}, children: [
], "bottom", body.depth + 0.1 + braceBody.height, options); {type: "elem", elem: braceBody},
{type: "kern", size: 0.1},
{type: "elem", elem: body},
],
}, options);
vlist.children[0].children[0].children[0].classes.push("svg-align"); vlist.children[0].children[0].children[0].classes.push("svg-align");
} }
@@ -1349,18 +1402,24 @@ groupTypes.horizBrace = function(group, options) {
[vlist], options); [vlist], options);
if (group.value.isOver) { if (group.value.isOver) {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList({
{type: "elem", elem: vSpan}, positionType: "firstBaseline",
{type: "kern", size: 0.2}, children: [
{type: "elem", elem: supSubGroup}, {type: "elem", elem: vSpan},
], "firstBaseline", null, options); {type: "kern", size: 0.2},
{type: "elem", elem: supSubGroup},
],
}, options);
} else { } else {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList({
{type: "elem", elem: supSubGroup}, positionType: "bottom",
{type: "kern", size: 0.2}, positionData: vSpan.depth + 0.2 + supSubGroup.height,
{type: "elem", elem: vSpan}, children: [
], "bottom", vSpan.depth + 0.2 + supSubGroup.height, {type: "elem", elem: supSubGroup},
options); {type: "kern", size: 0.2},
{type: "elem", elem: vSpan},
],
}, options);
} }
} }
@@ -1376,11 +1435,15 @@ groupTypes.accentUnder = function(group, options) {
const kern = (/tilde/.test(group.value.label) ? 0.12 : 0); const kern = (/tilde/.test(group.value.label) ? 0.12 : 0);
// Generate the vlist, with the appropriate kerns // Generate the vlist, with the appropriate kerns
const vlist = buildCommon.makeVList([ const vlist = buildCommon.makeVList({
{type: "elem", elem: accentBody}, positionType: "bottom",
{type: "kern", size: kern}, positionData: accentBody.height + kern,
{type: "elem", elem: innerGroup}, children: [
], "bottom", accentBody.height + kern, options); {type: "elem", elem: accentBody},
{type: "kern", size: kern},
{type: "elem", elem: innerGroup},
],
}, options);
vlist.children[0].children[0].children[0].classes.push("svg-align"); vlist.children[0].children[0].children[0].classes.push("svg-align");
@@ -1429,17 +1492,23 @@ groupTypes.enclose = function(group, options) {
let vlist; let vlist;
if (isColorbox) { if (isColorbox) {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList({
// Put the color background behind inner; positionType: "individualShift",
{type: "elem", elem: img, shift: imgShift}, children: [
{type: "elem", elem: inner, shift: 0}, // Put the color background behind inner;
], "individualShift", null, options); {type: "elem", elem: img, shift: imgShift},
{type: "elem", elem: inner, shift: 0},
],
}, options);
} else { } else {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList({
// Write the \cancel stroke on top of inner. positionType: "individualShift",
{type: "elem", elem: inner, shift: 0}, children: [
{type: "elem", elem: img, shift: imgShift}, // Write the \cancel stroke on top of inner.
], "individualShift", null, options); {type: "elem", elem: inner, shift: 0},
{type: "elem", elem: img, shift: imgShift},
],
}, options);
} }
if (/cancel/.test(label)) { if (/cancel/.test(label)) {
@@ -1487,16 +1556,22 @@ groupTypes.xArrow = function(group, options) {
const lowerShift = -options.fontMetrics().axisHeight const lowerShift = -options.fontMetrics().axisHeight
+ lowerGroup.height + 0.5 * arrowBody.height + lowerGroup.height + 0.5 * arrowBody.height
+ 0.111; + 0.111;
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList({
{type: "elem", elem: upperGroup, shift: upperShift}, positionType: "individualShift",
{type: "elem", elem: arrowBody, shift: arrowShift}, children: [
{type: "elem", elem: lowerGroup, shift: lowerShift}, {type: "elem", elem: upperGroup, shift: upperShift},
], "individualShift", null, options); {type: "elem", elem: arrowBody, shift: arrowShift},
{type: "elem", elem: lowerGroup, shift: lowerShift},
],
}, options);
} else { } else {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList({
{type: "elem", elem: upperGroup, shift: upperShift}, positionType: "individualShift",
{type: "elem", elem: arrowBody, shift: arrowShift}, children: [
], "individualShift", null, options); {type: "elem", elem: upperGroup, shift: upperShift},
{type: "elem", elem: arrowBody, shift: arrowShift},
],
}, options);
} }
vlist.children[0].children[0].children[1].classes.push("svg-align"); vlist.children[0].children[0].children[1].classes.push("svg-align");
@@ -1522,10 +1597,11 @@ groupTypes.raisebox = function(group, options) {
size: 6, // simulate \normalsize size: 6, // simulate \normalsize
}}, options); }}, options);
const dy = calculateSize(group.value.dy.value, options); const dy = calculateSize(group.value.dy.value, options);
return buildCommon.makeVList([{ return buildCommon.makeVList({
type: "elem", positionType: "shift",
elem: body, positionData: -dy,
}], "shift", -dy, options); children: [{type: "elem", elem: body}],
}, options);
}; };
/** /**

View File

@@ -307,7 +307,11 @@ const makeStackedDelim = function(delim, heightTotal, center, options, mode,
// Finally, build the vlist // Finally, build the vlist
const newOptions = options.havingBaseStyle(Style.TEXT); const newOptions = options.havingBaseStyle(Style.TEXT);
const inner = buildCommon.makeVList(inners, "bottom", depth, newOptions); const inner = buildCommon.makeVList({
positionType: "bottom",
positionData: depth,
children: inners,
}, newOptions);
return styleWrap( return styleWrap(
buildCommon.makeSpan(["delimsizing", "mult"], [inner], newOptions), buildCommon.makeSpan(["delimsizing", "mult"], [inner], newOptions),

View File

@@ -243,7 +243,10 @@ const htmlBuilder = function(group, options) {
col.push({type: "elem", elem: elem, shift: shift}); col.push({type: "elem", elem: elem, shift: shift});
} }
col = buildCommon.makeVList(col, "individualShift", null, options); col = buildCommon.makeVList({
positionType: "individualShift",
children: col,
}, options);
col = buildCommon.makeSpan( col = buildCommon.makeSpan(
["col-align-" + (colDescr.align || "c")], ["col-align-" + (colDescr.align || "c")],
[col]); [col]);

View File

@@ -63,9 +63,10 @@ defineFunction({
} }
// See smash for comment re: use of makeVList // See smash for comment re: use of makeVList
node = buildCommon.makeVList([ node = buildCommon.makeVList({
{type: "elem", elem: node}, positionType: "firstBaseline",
], "firstBaseline", null, options); children: [{type: "elem", elem: node}],
}, options);
return node; return node;
}, },