Format buildHTML groupType definitions in preparation for adding types. (#1206)

* Format buildHTML groupType definitions in preparation for adding types.

1) Collapse groupType definitions (a bunch of functions all of the same type
   defined on the groupType object) into a single object in preparation for
   putting types only on the base object.
2) Do minor reformatting to account for the +4 indent above which pushes some
   lines past 80 characters.
3) Do other minor formatting changes for readability, taking advantage of ES6.

* Review fixes.
This commit is contained in:
Ashish Myles
2018-03-14 10:38:35 -04:00
committed by ylemkimon
parent 65b47430b5
commit 5bcdeec4ad

View File

@@ -63,8 +63,7 @@ export const buildExpression = function(expression, options, isRealGroup,
// Parse expressions into `groups`.
const rawGroups = [];
for (let i = 0; i < expression.length; i++) {
const group = expression[i];
const output = buildGroup(group, options);
const output = buildGroup(expression[i], options);
if (output instanceof domTree.documentFragment) {
rawGroups.push(...output.children);
} else {
@@ -216,24 +215,22 @@ export const isLeftTight = function(node) {
* handling them itself.
*/
const shouldHandleSupSub = function(group, options) {
if (!group.value.base) {
const base = group.value.base;
if (!base) {
return false;
} else if (base.type === "op") {
// Operators handle supsubs differently when they have limits
// (e.g. `\displaystyle\sum_2^3`)
return base.value.limits &&
(options.style.size === Style.DISPLAY.size ||
base.value.alwaysHandleSupSub);
} else if (base.type === "accent") {
return utils.isCharacterBox(base.value.base);
} else if (base.type === "horizBrace") {
const isSup = !group.value.sub;
return (isSup === base.value.isOver);
} else {
const base = group.value.base;
if (base.type === "op") {
// Operators handle supsubs differently when they have limits
// (e.g. `\displaystyle\sum_2^3`)
return base.value.limits &&
(options.style.size === Style.DISPLAY.size ||
base.value.alwaysHandleSupSub);
} else if (base.type === "accent") {
return utils.isCharacterBox(base.value.base);
} else if (base.type === "horizBrace") {
const isSup = (group.value.sub ? false : true);
return (isSup === base.value.isOver);
} else {
return null;
}
return false;
}
};
@@ -246,374 +243,354 @@ export const makeNullDelimiter = function(options, classes) {
* This is a map of group types to the function used to handle that type.
* Simpler types come at the beginning, while complicated types come afterwards.
*/
export const groupTypes = {};
export const groupTypes = {
mathord: (group, options) => buildCommon.makeOrd(group, options, "mathord"),
groupTypes.mathord = function(group, options) {
return buildCommon.makeOrd(group, options, "mathord");
};
textord: (group, options) => buildCommon.makeOrd(group, options, "textord"),
groupTypes.textord = function(group, options) {
return buildCommon.makeOrd(group, options, "textord");
};
bin: (group, options) =>
buildCommon.mathsym(group.value, group.mode, options, ["mbin"]),
groupTypes.bin = function(group, options) {
return buildCommon.mathsym(
group.value, group.mode, options, ["mbin"]);
};
rel: (group, options) =>
buildCommon.mathsym(group.value, group.mode, options, ["mrel"]),
groupTypes.rel = function(group, options) {
return buildCommon.mathsym(
group.value, group.mode, options, ["mrel"]);
};
open: (group, options) =>
buildCommon.mathsym(group.value, group.mode, options, ["mopen"]),
groupTypes.open = function(group, options) {
return buildCommon.mathsym(
group.value, group.mode, options, ["mopen"]);
};
close: (group, options) =>
buildCommon.mathsym(group.value, group.mode, options, ["mclose"]),
groupTypes.close = function(group, options) {
return buildCommon.mathsym(
group.value, group.mode, options, ["mclose"]);
};
inner: (group, options) =>
buildCommon.mathsym(group.value, group.mode, options, ["minner"]),
groupTypes.inner = function(group, options) {
return buildCommon.mathsym(
group.value, group.mode, options, ["minner"]);
};
punct: (group, options) =>
buildCommon.mathsym(group.value, group.mode, options, ["mpunct"]),
groupTypes.punct = function(group, options) {
return buildCommon.mathsym(
group.value, group.mode, options, ["mpunct"]);
};
ordgroup: (group, options) => makeSpan(
["mord"], buildExpression(group.value, options, true), options),
groupTypes.ordgroup = function(group, options) {
return makeSpan(["mord"],
buildExpression(group.value, options, true),
options
);
};
supsub(group, options) {
// Superscript and subscripts are handled in the TeXbook on page
// 445-446, rules 18(a-f).
groupTypes.supsub = function(group, options) {
// Superscript and subscripts are handled in the TeXbook on page
// 445-446, rules 18(a-f).
// Here is where we defer to the inner group if it should handle
// superscripts and subscripts itself.
if (shouldHandleSupSub(group, options)) {
return groupTypes[group.value.base.type](group, options);
}
const base = buildGroup(group.value.base, options);
let supm;
let subm;
const metrics = options.fontMetrics();
let newOptions;
// Rule 18a
let supShift = 0;
let subShift = 0;
if (group.value.sup) {
newOptions = options.havingStyle(options.style.sup());
supm = buildGroup(group.value.sup, newOptions, options);
if (!utils.isCharacterBox(group.value.base)) {
supShift = base.height - newOptions.fontMetrics().supDrop
* newOptions.sizeMultiplier / options.sizeMultiplier;
}
}
if (group.value.sub) {
newOptions = options.havingStyle(options.style.sub());
subm = buildGroup(group.value.sub, newOptions, options);
if (!utils.isCharacterBox(group.value.base)) {
subShift = base.depth + newOptions.fontMetrics().subDrop
* newOptions.sizeMultiplier / options.sizeMultiplier;
}
}
// Rule 18c
let minSupShift;
if (options.style === Style.DISPLAY) {
minSupShift = metrics.sup1;
} else if (options.style.cramped) {
minSupShift = metrics.sup3;
} else {
minSupShift = metrics.sup2;
}
// scriptspace is a font-size-independent size, so scale it
// appropriately
const multiplier = options.sizeMultiplier;
const scriptspace =
(0.5 / metrics.ptPerEm) / multiplier + "em";
let supsub;
if (!group.value.sup) {
// Rule 18b
subShift = Math.max(
subShift, metrics.sub1,
subm.height - 0.8 * metrics.xHeight);
const vlistElem = [{type: "elem", elem: subm, marginRight: scriptspace}];
// Subscripts shouldn't be shifted by the base's italic correction.
// Account for that by shifting the subscript back the appropriate
// amount. Note we only do this when the base is a single symbol.
if (base instanceof domTree.symbolNode) {
vlistElem[0].marginLeft = -base.italic + "em";
// Here is where we defer to the inner group if it should handle
// superscripts and subscripts itself.
if (shouldHandleSupSub(group, options)) {
return groupTypes[group.value.base.type](group, options);
}
supsub = buildCommon.makeVList({
positionType: "shift",
positionData: subShift,
children: vlistElem,
}, options);
} else if (!group.value.sub) {
// Rule 18c, d
supShift = Math.max(supShift, minSupShift,
supm.depth + 0.25 * metrics.xHeight);
const base = buildGroup(group.value.base, options);
let supm;
let subm;
supsub = buildCommon.makeVList({
positionType: "shift",
positionData: -supShift,
children: [{type: "elem", elem: supm, marginRight: scriptspace}],
}, options);
} else {
supShift = Math.max(
supShift, minSupShift, supm.depth + 0.25 * metrics.xHeight);
subShift = Math.max(subShift, metrics.sub2);
const metrics = options.fontMetrics();
let newOptions;
const ruleWidth = metrics.defaultRuleThickness;
// Rule 18a
let supShift = 0;
let subShift = 0;
// Rule 18e
if ((supShift - supm.depth) - (subm.height - subShift) <
4 * ruleWidth) {
subShift = 4 * ruleWidth - (supShift - supm.depth) + subm.height;
const psi = 0.8 * metrics.xHeight - (supShift - supm.depth);
if (psi > 0) {
supShift += psi;
subShift -= psi;
if (group.value.sup) {
newOptions = options.havingStyle(options.style.sup());
supm = buildGroup(group.value.sup, newOptions, options);
if (!utils.isCharacterBox(group.value.base)) {
supShift = base.height - newOptions.fontMetrics().supDrop
* newOptions.sizeMultiplier / options.sizeMultiplier;
}
}
const vlistElem = [
{type: "elem", elem: subm, shift: subShift, marginRight: scriptspace},
{type: "elem", elem: supm, shift: -supShift, marginRight: scriptspace},
];
// See comment above about subscripts not being shifted
if (base instanceof domTree.symbolNode) {
vlistElem[0].marginLeft = -base.italic + "em";
if (group.value.sub) {
newOptions = options.havingStyle(options.style.sub());
subm = buildGroup(group.value.sub, newOptions, options);
if (!utils.isCharacterBox(group.value.base)) {
subShift = base.depth + newOptions.fontMetrics().subDrop
* newOptions.sizeMultiplier / options.sizeMultiplier;
}
}
supsub = buildCommon.makeVList({
positionType: "individualShift",
children: vlistElem,
}, options);
}
// We ensure to wrap the supsub vlist in a span.msupsub to reset text-align
const mclass = getTypeOfDomTree(base) || "mord";
return makeSpan([mclass],
[base, makeSpan(["msupsub"], [supsub])],
options);
};
groupTypes.spacing = function(group, options) {
if (buildCommon.regularSpace.hasOwnProperty(group.value)) {
// Spaces are generated by adding an actual space. Each of these
// things has an entry in the symbols table, so these will be turned
// into appropriate outputs.
if (group.mode === "text") {
return buildCommon.makeOrd(group, options, "textord");
// Rule 18c
let minSupShift;
if (options.style === Style.DISPLAY) {
minSupShift = metrics.sup1;
} else if (options.style.cramped) {
minSupShift = metrics.sup3;
} else {
return makeSpan(["mspace"],
[buildCommon.mathsym(group.value, group.mode, options)],
options);
minSupShift = metrics.sup2;
}
} else {
// Other kinds of spaces are of arbitrary width. We use CSS to
// generate these.
return makeSpan(
["mspace", buildCommon.spacingFunctions[group.value].className],
[], options);
}
};
groupTypes.horizBrace = function(group, options) {
const style = options.style;
// scriptspace is a font-size-independent size, so scale it
// appropriately for use as the marginRight.
const multiplier = options.sizeMultiplier;
const marginRight = (0.5 / metrics.ptPerEm) / multiplier + "em";
const hasSupSub = (group.type === "supsub");
let supSubGroup;
let newOptions;
if (hasSupSub) {
// Ref: LaTeX source2e: }}}}\limits}
// i.e. LaTeX treats the brace similar to an op and passes it
// with \limits, so we need to assign supsub style.
if (group.value.sup) {
newOptions = options.havingStyle(style.sup());
supSubGroup = buildGroup(group.value.sup, newOptions, options);
let supsub;
if (!group.value.sup) {
// Rule 18b
subShift = Math.max(
subShift, metrics.sub1,
subm.height - 0.8 * metrics.xHeight);
const vlistElem = [{type: "elem", elem: subm, marginRight}];
// Subscripts shouldn't be shifted by the base's italic correction.
// Account for that by shifting the subscript back the appropriate
// amount. Note we only do this when the base is a single symbol.
if (base instanceof domTree.symbolNode) {
vlistElem[0].marginLeft = -base.italic + "em";
}
supsub = buildCommon.makeVList({
positionType: "shift",
positionData: subShift,
children: vlistElem,
}, options);
} else if (!group.value.sub) {
// Rule 18c, d
supShift = Math.max(supShift, minSupShift,
supm.depth + 0.25 * metrics.xHeight);
supsub = buildCommon.makeVList({
positionType: "shift",
positionData: -supShift,
children: [{type: "elem", elem: supm, marginRight}],
}, options);
} else {
newOptions = options.havingStyle(style.sub());
supSubGroup = buildGroup(group.value.sub, newOptions, options);
supShift = Math.max(
supShift, minSupShift, supm.depth + 0.25 * metrics.xHeight);
subShift = Math.max(subShift, metrics.sub2);
const ruleWidth = metrics.defaultRuleThickness;
// Rule 18e
const maxWidth = 4 * ruleWidth;
if ((supShift - supm.depth) - (subm.height - subShift) < maxWidth) {
subShift = maxWidth - (supShift - supm.depth) + subm.height;
const psi = 0.8 * metrics.xHeight - (supShift - supm.depth);
if (psi > 0) {
supShift += psi;
subShift -= psi;
}
}
const vlistElem = [
{type: "elem", elem: subm, shift: subShift, marginRight},
{type: "elem", elem: supm, shift: -supShift, marginRight},
];
// See comment above about subscripts not being shifted.
if (base instanceof domTree.symbolNode) {
vlistElem[0].marginLeft = -base.italic + "em";
}
supsub = buildCommon.makeVList({
positionType: "individualShift",
children: vlistElem,
}, options);
}
group = group.value.base;
}
// Build the base group
const body = buildGroup(
group.value.base, options.havingBaseStyle(Style.DISPLAY));
// Wrap the supsub vlist in a span.msupsub to reset text-align.
const mclass = getTypeOfDomTree(base) || "mord";
return makeSpan([mclass],
[base, makeSpan(["msupsub"], [supsub])],
options);
},
// Create the stretchy element
const braceBody = stretchy.svgSpan(group, options);
spacing(group, options) {
if (buildCommon.regularSpace.hasOwnProperty(group.value)) {
// Spaces are generated by adding an actual space. Each of these
// things has an entry in the symbols table, so these will be turned
// into appropriate outputs.
if (group.mode === "text") {
return buildCommon.makeOrd(group, options, "textord");
} else {
return makeSpan(["mspace"],
[buildCommon.mathsym(group.value, group.mode, options)],
options);
}
} else {
// Other kinds of spaces are of arbitrary width. We use CSS to
// generate these.
return makeSpan(
["mspace", buildCommon.spacingFunctions[group.value].className],
[], options);
}
},
// Generate the vlist, with the appropriate kerns ┏━━━━━━━━┓
// This first vlist contains the subject matter and the brace: equation
let vlist;
if (group.value.isOver) {
vlist = buildCommon.makeVList({
positionType: "firstBaseline",
children: [
{type: "elem", elem: body},
{type: "kern", size: 0.1},
{type: "elem", elem: braceBody},
],
}, options);
vlist.children[0].children[0].children[1].classes.push("svg-align");
} else {
vlist = buildCommon.makeVList({
positionType: "bottom",
positionData: body.depth + 0.1 + braceBody.height,
children: [
{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");
}
horizBrace(group, options) {
const style = options.style;
if (hasSupSub) {
// In order to write the supsub, wrap the first vlist in another vlist:
// They can't all go in the same vlist, because the note might be wider
// than the equation. We want the equation to control the brace width.
const hasSupSub = (group.type === "supsub");
let supSubGroup;
let newOptions;
if (hasSupSub) {
// Ref: LaTeX source2e: }}}}\limits}
// i.e. LaTeX treats the brace similar to an op and passes it
// with \limits, so we need to assign supsub style.
if (group.value.sup) {
newOptions = options.havingStyle(style.sup());
supSubGroup = buildGroup(group.value.sup, newOptions, options);
} else {
newOptions = options.havingStyle(style.sub());
supSubGroup = buildGroup(group.value.sub, newOptions, options);
}
group = group.value.base;
}
// note long note long note
// ┏━━━━━━━━┓ or ┏━━━┓ not ┏━━━━━━━━━┓
// equation eqn eqn
// Build the base group
const body = buildGroup(
group.value.base, options.havingBaseStyle(Style.DISPLAY));
const vSpan = makeSpan(["mord",
(group.value.isOver ? "mover" : "munder")],
[vlist], options);
// Create the stretchy element
const braceBody = stretchy.svgSpan(group, options);
// Generate the vlist, with the appropriate kerns ┏━━━━━━━━┓
// This first vlist contains the content and the brace: equation
let vlist;
if (group.value.isOver) {
vlist = buildCommon.makeVList({
positionType: "firstBaseline",
children: [
{type: "elem", elem: vSpan},
{type: "kern", size: 0.2},
{type: "elem", elem: supSubGroup},
{type: "elem", elem: body},
{type: "kern", size: 0.1},
{type: "elem", elem: braceBody},
],
}, options);
vlist.children[0].children[0].children[1].classes.push("svg-align");
} else {
vlist = buildCommon.makeVList({
positionType: "bottom",
positionData: body.depth + 0.1 + braceBody.height,
children: [
{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");
}
if (hasSupSub) {
// To write the supsub, wrap the first vlist in another vlist:
// They can't all go in the same vlist, because the note might be
// wider than the equation. We want the equation to control the
// brace width.
// note long note long note
// ┏━━━━━━━━┓ or ┏━━━┓ not ┏━━━━━━━━━┓
// equation eqn eqn
const vSpan = makeSpan(
["mord", (group.value.isOver ? "mover" : "munder")],
[vlist], options);
if (group.value.isOver) {
vlist = buildCommon.makeVList({
positionType: "firstBaseline",
children: [
{type: "elem", elem: vSpan},
{type: "kern", size: 0.2},
{type: "elem", elem: supSubGroup},
],
}, options);
} else {
vlist = buildCommon.makeVList({
positionType: "bottom",
positionData: vSpan.depth + 0.2 + supSubGroup.height,
children: [
{type: "elem", elem: supSubGroup},
{type: "kern", size: 0.2},
{type: "elem", elem: vSpan},
],
}, options);
}
}
return makeSpan(["mord", (group.value.isOver ? "mover" : "munder")],
[vlist], options);
},
xArrow(group, options) {
const style = options.style;
// Build the argument groups in the appropriate style.
// Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}%
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);
// Re shift: Note that stretchy.svgSpan returned arrowBody.depth = 0.
// The point we want on the math axis is at 0.5 * arrowBody.height.
const arrowShift = -options.fontMetrics().axisHeight +
0.5 * arrowBody.height;
// 2 mu kern. Ref: amsmath.dtx: #7\if0#2\else\mkern#2mu\fi
let upperShift =
-options.fontMetrics().axisHeight - 0.5 * arrowBody.height - 0.111;
if (group.value.label === "\\xleftequilibrium") {
upperShift -= upperGroup.depth;
}
// Generate the vlist
let vlist;
if (group.value.below) {
const lowerShift = -options.fontMetrics().axisHeight
+ lowerGroup.height + 0.5 * arrowBody.height
+ 0.111;
vlist = buildCommon.makeVList({
positionType: "individualShift",
children: [
{type: "elem", elem: upperGroup, shift: upperShift},
{type: "elem", elem: arrowBody, shift: arrowShift},
{type: "elem", elem: lowerGroup, shift: lowerShift},
],
}, options);
} else {
vlist = buildCommon.makeVList({
positionType: "bottom",
positionData: vSpan.depth + 0.2 + supSubGroup.height,
positionType: "individualShift",
children: [
{type: "elem", elem: supSubGroup},
{type: "kern", size: 0.2},
{type: "elem", elem: vSpan},
{type: "elem", elem: upperGroup, shift: upperShift},
{type: "elem", elem: arrowBody, shift: arrowShift},
],
}, options);
}
}
return makeSpan(["mord", (group.value.isOver ? "mover" : "munder")],
[vlist], options);
};
vlist.children[0].children[0].children[1].classes.push("svg-align");
groupTypes.xArrow = function(group, options) {
const style = options.style;
return makeSpan(["mrel", "x-arrow"], [vlist], options);
},
// Build the argument groups in the appropriate style.
// Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}%
mclass(group, options) {
const elements = buildExpression(group.value.value, options, true);
let newOptions = options.havingStyle(style.sup());
const upperGroup = buildGroup(group.value.body, newOptions, options);
upperGroup.classes.push("x-arrow-pad");
return makeSpan([group.value.mclass], elements, options);
},
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);
// Re shift: Note that stretchy.svgSpan returned arrowBody.depth = 0.
// The point we want on the math axis is at 0.5 * arrowBody.height.
const arrowShift = -options.fontMetrics().axisHeight +
0.5 * arrowBody.height;
// 2 mu kern. Ref: amsmath.dtx: #7\if0#2\else\mkern#2mu\fi
let upperShift = -options.fontMetrics().axisHeight -
0.5 * arrowBody.height - 0.111;
if (group.value.label === "\\xleftequilibrium") {
upperShift -= upperGroup.depth;
}
// Generate the vlist
let vlist;
if (group.value.below) {
const lowerShift = -options.fontMetrics().axisHeight
+ lowerGroup.height + 0.5 * arrowBody.height
+ 0.111;
vlist = buildCommon.makeVList({
positionType: "individualShift",
children: [
{type: "elem", elem: upperGroup, shift: upperShift},
{type: "elem", elem: arrowBody, shift: arrowShift},
{type: "elem", elem: lowerGroup, shift: lowerShift},
],
raisebox(group, options) {
const body = groupTypes.sizing({value: {
value: [{
type: "text",
value: {
body: group.value.value,
font: "mathrm", // simulate \textrm
},
}],
size: 6, // simulate \normalsize
}}, options);
const dy = calculateSize(group.value.dy.value, options);
return buildCommon.makeVList({
positionType: "shift",
positionData: -dy,
children: [{type: "elem", elem: body}],
}, options);
} else {
vlist = buildCommon.makeVList({
positionType: "individualShift",
children: [
{type: "elem", elem: upperGroup, shift: upperShift},
{type: "elem", elem: arrowBody, shift: arrowShift},
],
}, options);
}
vlist.children[0].children[0].children[1].classes.push("svg-align");
return makeSpan(["mrel", "x-arrow"], [vlist], options);
};
groupTypes.mclass = function(group, options) {
const elements = buildExpression(group.value.value, options, true);
return makeSpan([group.value.mclass], elements, options);
};
groupTypes.raisebox = function(group, options) {
const body = groupTypes.sizing({value: {
value: [{
type: "text",
value: {
body: group.value.value,
font: "mathrm", // simulate \textrm
},
}],
size: 6, // simulate \normalsize
}}, options);
const dy = calculateSize(group.value.dy.value, options);
return buildCommon.makeVList({
positionType: "shift",
positionData: -dy,
children: [{type: "elem", elem: body}],
}, options);
},
};
/**
@@ -636,8 +613,8 @@ export const buildGroup = function(group, options, baseOptions) {
groupNode = makeSpan(options.sizingClasses(baseOptions),
[groupNode], options);
const multiplier = options.sizeMultiplier /
baseOptions.sizeMultiplier;
const multiplier =
options.sizeMultiplier / baseOptions.sizeMultiplier;
groupNode.height *= multiplier;
groupNode.depth *= multiplier;