Fix interaction between styles and sizes. (#719)

* Fix interaction between styles and sizes by implementing styles as sizes.

Rather than having both `textstyle` CSS classes and `size5` CSS classes
affect the font size (and step on each other), implement sizes more the
way TeX does: a command like `\displaystyle` changes the current size.

This is actually a simplification, since now only `size` affects the size.
Simplifies CSS and computation. Many screenshotter tests change; they
change to be more like TeX. For instance, `\sqrt` fixes some
discrepancies in size treatment.

Also:

Remove the `Options.withX()` methods in favor of `.havingX()`, which
might return the same `options`.

Remove `Style.cls()` and `Style.reset()`.

Remove `Options.reset()`. You should never modify an `Options`; they
should change only by the `havingX()` methods.

* Implement TeX sizing for scriptsize/scriptscriptsize.

At every size level. Also make the sizes match TeX to the last decimal.

* Review comments.
This commit is contained in:
Eddie Kohler
2017-06-27 20:55:14 -04:00
committed by Kevin Barabash
parent 4f57d53f6e
commit b866cd5224
29 changed files with 347 additions and 410 deletions

View File

@@ -5,33 +5,43 @@
* `.reset` functions. * `.reset` functions.
*/ */
const BASESIZE = 6;
const sizeStyleMap = [
// Each element contains [textsize, scriptsize, scriptscriptsize].
// The size mappings are taken from TeX with \normalsize=10pt.
[1, 1, 1], // size1: [5, 5, 5] \tiny
[2, 1, 1], // size2: [6, 5, 5]
[3, 1, 1], // size3: [7, 5, 5] \scriptsize
[4, 2, 1], // size4: [8, 6, 5] \footnotesize
[5, 2, 1], // size5: [9, 6, 5] \small
[6, 3, 1], // size6: [10, 7, 5] \normalsize
[7, 4, 2], // size7: [12, 8, 6] \large
[8, 6, 3], // size8: [14.4, 10, 7] \Large
[9, 7, 6], // size9: [17.28, 12, 10] \LARGE
[10, 8, 7], // size10: [20.74, 14.4, 12] \huge
[11, 10, 9], // size11: [24.88, 20.74, 17.28] \HUGE
];
const sizeMultipliers = [
0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.44, 1.728, 2.074, 2.488,
];
/** /**
* This is the main options class. It contains the style, size, color, and font * This is the main options class. It contains the current style, size, color,
* of the current parse level. It also contains the style and size of the parent * and font.
* parse level, so size changes can be handled efficiently.
* *
* Each of the `.with*` and `.reset` functions passes its current style and size * Options objects should not be modified. To create a new Options with
* as the parentStyle and parentSize of the new options class, so parent * different properties, call a `.having*` method.
* handling is taken care of automatically.
*/ */
function Options(data) { function Options(data) {
this.style = data.style; this.style = data.style;
this.color = data.color; this.color = data.color;
this.size = data.size; this.size = data.size || BASESIZE;
this.textSize = data.textSize || this.size;
this.phantom = data.phantom; this.phantom = data.phantom;
this.font = data.font; this.font = data.font;
this.sizeMultiplier = sizeMultipliers[this.size - 1];
if (data.parentStyle === undefined) {
this.parentStyle = data.style;
} else {
this.parentStyle = data.parentStyle;
}
if (data.parentSize === undefined) {
this.parentSize = data.size;
} else {
this.parentSize = data.parentSize;
}
} }
/** /**
@@ -42,9 +52,8 @@ Options.prototype.extend = function(extension) {
const data = { const data = {
style: this.style, style: this.style,
size: this.size, size: this.size,
textSize: this.textSize,
color: this.color, color: this.color,
parentStyle: this.style,
parentSize: this.size,
phantom: this.phantom, phantom: this.phantom,
font: this.font, font: this.font,
}; };
@@ -58,22 +67,66 @@ Options.prototype.extend = function(extension) {
return new Options(data); return new Options(data);
}; };
function sizeAtStyle(size, style) {
return style.size < 2 ? size : sizeStyleMap[size - 1][style.size - 1];
}
/** /**
* Create a new options object with the given style. * Return an options object with the given style. If `this.style === style`,
* returns `this`.
*/ */
Options.prototype.withStyle = function(style) { Options.prototype.havingStyle = function(style) {
return this.extend({ if (this.style === style) {
style: style, return this;
}); } else {
return this.extend({
style: style,
size: sizeAtStyle(this.textSize, style),
});
}
}; };
/** /**
* Create a new options object with the given size. * Return an options object with a cramped version of the current style. If
* the current style is cramped, returns `this`.
*/ */
Options.prototype.withSize = function(size) { Options.prototype.havingCrampedStyle = function() {
return this.extend({ return this.havingStyle(this.style.cramp());
size: size, };
});
/**
* Return an options object with the given size and in at least `\textstyle`.
* Returns `this` if appropriate.
*/
Options.prototype.havingSize = function(size) {
if (this.size === size && this.textSize === size) {
return this;
} else {
return this.extend({
style: this.style.text(),
size: size,
textSize: size,
});
}
};
/**
* Like `this.havingSize(BASESIZE).havingStyle(style)`. If `style` is omitted,
* changes to at least `\textstyle`.
*/
Options.prototype.havingBaseStyle = function(style) {
style = style || this.style.text();
const wantSize = sizeAtStyle(BASESIZE, style);
if (this.size === wantSize && this.textSize === BASESIZE
&& this.style === style) {
return this;
} else {
return this.extend({
style: style,
size: wantSize,
baseSize: BASESIZE,
});
}
}; };
/** /**
@@ -104,11 +157,27 @@ Options.prototype.withFont = function(font) {
}; };
/** /**
* Create a new options object with the same style, size, and color. This is * Return the CSS sizing classes required to switch from enclosing options
* used so that parent style and size changes are handled correctly. * `oldOptions` to `this`. Returns an array of classes.
*/ */
Options.prototype.reset = function() { Options.prototype.sizingClasses = function(oldOptions) {
return this.extend({}); if (oldOptions.size !== this.size) {
return ["sizing", "reset-size" + oldOptions.size, "size" + this.size];
} else {
return [];
}
};
/**
* Return the CSS sizing classes required to switch to the base size. Like
* `this.havingSize(BASESIZE).sizingClasses(this)`.
*/
Options.prototype.baseSizingClasses = function() {
if (this.size !== BASESIZE) {
return ["sizing", "reset-size" + this.size, "size" + BASESIZE];
} else {
return [];
}
}; };
/** /**
@@ -186,4 +255,9 @@ Options.prototype.getColor = function() {
} }
}; };
/**
* The base size index.
*/
Options.BASESIZE = BASESIZE;
module.exports = Options; module.exports = Options;

View File

@@ -390,8 +390,8 @@ Parser.prototype.parseAtom = function() {
// A list of the size-changing functions, for use in parseImplicitGroup // A list of the size-changing functions, for use in parseImplicitGroup
const sizeFuncs = [ const sizeFuncs = [
"\\tiny", "\\scriptsize", "\\footnotesize", "\\small", "\\normalsize", "\\tiny", "\\sixptsize", "\\scriptsize", "\\footnotesize", "\\small",
"\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge", "\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge",
]; ];
// A list of the style-changing functions, for use in parseImplicitGroup // A list of the style-changing functions, for use in parseImplicitGroup
@@ -483,7 +483,7 @@ Parser.prototype.parseImplicitGroup = function() {
const body = this.parseExpression(false); const body = this.parseExpression(false);
return new ParseNode("sizing", { return new ParseNode("sizing", {
// Figure out what size to use based on the list of functions above // Figure out what size to use based on the list of functions above
size: "size" + (utils.indexOf(sizeFuncs, func) + 1), size: utils.indexOf(sizeFuncs, func) + 1,
value: body, value: body,
}, this.mode); }, this.mode);
} else if (utils.contains(styleFuncs, func)) { } else if (utils.contains(styleFuncs, func)) {

View File

@@ -22,15 +22,12 @@ for (let i = 0; i < 3; i++) {
/** /**
* The main style class. Contains a unique id for the style, a size (which is * The main style class. Contains a unique id for the style, a size (which is
* the same for cramped and uncramped version of a style), a cramped flag, and a * the same for cramped and uncramped version of a style), and a cramped flag.
* size multiplier, which gives the size difference between a style and
* textstyle.
*/ */
function Style(id, size, multiplier, cramped) { function Style(id, size, cramped) {
this.id = id; this.id = id;
this.size = size; this.size = size;
this.cramped = cramped; this.cramped = cramped;
this.sizeMultiplier = multiplier;
this.metrics = metrics[size > 0 ? size - 1 : 0]; this.metrics = metrics[size > 0 ? size - 1 : 0];
} }
@@ -73,17 +70,10 @@ Style.prototype.cramp = function() {
}; };
/** /**
* HTML class name, like "displaystyle cramped" * Get a text or display version of this style.
*/ */
Style.prototype.cls = function() { Style.prototype.text = function() {
return sizeNames[this.size] + (this.cramped ? " cramped" : " uncramped"); return styles[text[this.id]];
};
/**
* HTML Reset class name, like "reset-textstyle"
*/
Style.prototype.reset = function() {
return resetNames[this.size];
}; };
/** /**
@@ -103,32 +93,16 @@ const Sc = 5;
const SS = 6; const SS = 6;
const SSc = 7; const SSc = 7;
// String names for the different sizes
const sizeNames = [
"displaystyle textstyle",
"textstyle",
"scriptstyle",
"scriptscriptstyle",
];
// Reset names for the different sizes
const resetNames = [
"reset-textstyle",
"reset-textstyle",
"reset-scriptstyle",
"reset-scriptscriptstyle",
];
// Instances of the different styles // Instances of the different styles
const styles = [ const styles = [
new Style(D, 0, 1.0, false), new Style(D, 0, false),
new Style(Dc, 0, 1.0, true), new Style(Dc, 0, true),
new Style(T, 1, 1.0, false), new Style(T, 1, false),
new Style(Tc, 1, 1.0, true), new Style(Tc, 1, true),
new Style(S, 2, 0.7, false), new Style(S, 2, false),
new Style(Sc, 2, 0.7, true), new Style(Sc, 2, true),
new Style(SS, 3, 0.5, false), new Style(SS, 3, false),
new Style(SSc, 3, 0.5, true), new Style(SSc, 3, true),
]; ];
// Lookup tables for switching from one style to another // Lookup tables for switching from one style to another
@@ -137,6 +111,7 @@ const sub = [Sc, Sc, Sc, Sc, SSc, SSc, SSc, SSc];
const fracNum = [T, Tc, S, Sc, SS, SSc, SS, SSc]; const fracNum = [T, Tc, S, Sc, SS, SSc, SS, SSc];
const fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, SSc, SSc]; const fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, SSc, SSc];
const cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc]; const cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc];
const text = [D, Dc, T, Tc, T, Tc, T, Tc];
// We only export some of the styles. Also, we don't export the `Style` class so // We only export some of the styles. Also, we don't export the `Style` class so
// no more styles can be generated. // no more styles can be generated.

View File

@@ -63,6 +63,7 @@ const makeSymbol = function(value, fontFamily, mode, options, classes) {
} }
if (options) { if (options) {
symbolNode.maxFontSize = options.sizeMultiplier;
if (options.style.isTight()) { if (options.style.isTight()) {
symbolNode.classes.push("mtight"); symbolNode.classes.push("mtight");
} }
@@ -239,11 +240,10 @@ const makeFragment = function(children) {
*/ */
const makeFontSizer = function(options, fontSize) { const makeFontSizer = function(options, fontSize) {
const fontSizeInner = makeSpan([], [new domTree.symbolNode("\u200b")]); const fontSizeInner = makeSpan([], [new domTree.symbolNode("\u200b")]);
fontSizeInner.style.fontSize = fontSizeInner.style.fontSize = fontSize + "em";
(fontSize / options.style.sizeMultiplier) + "em";
const fontSizer = makeSpan( const fontSizer = makeSpan(
["fontsize-ensurer", "reset-" + options.size, "size5"], ["fontsize-ensurer"].concat(options.baseSizingClasses()),
[fontSizeInner]); [fontSizeInner]);
return fontSizer; return fontSizer;
@@ -378,20 +378,6 @@ const makeVList = function(children, positionType, positionData, options) {
return vlist; return vlist;
}; };
// A table of size -> font size for the different sizing functions
const sizingMultiplier = {
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,
};
// A map of spacing functions to their attributes, like size and corresponding // A map of spacing functions to their attributes, like size and corresponding
// CSS class // CSS class
const spacingFunctions = { const spacingFunctions = {
@@ -486,6 +472,5 @@ module.exports = {
makeVList: makeVList, makeVList: makeVList,
makeOrd: makeOrd, makeOrd: makeOrd,
prependChildren: prependChildren, prependChildren: prependChildren,
sizingMultiplier: sizingMultiplier,
spacingFunctions: spacingFunctions, spacingFunctions: spacingFunctions,
}; };

View File

@@ -216,10 +216,8 @@ const isCharacterBox = function(group) {
}; };
const makeNullDelimiter = function(options, classes) { const makeNullDelimiter = function(options, classes) {
return makeSpan(classes.concat([ const moreClasses = ["nulldelimiter"].concat(options.baseSizingClasses());
"sizing", "reset-" + options.size, "size5", return makeSpan(classes.concat(moreClasses));
options.style.reset(), Style.TEXT.cls(),
"nulldelimiter"]));
}; };
/** /**
@@ -267,9 +265,8 @@ groupTypes.punct = function(group, options) {
}; };
groupTypes.ordgroup = function(group, options) { groupTypes.ordgroup = function(group, options) {
return makeSpan( return makeSpan(["mord"],
["mord", options.style.cls()], buildExpression(group.value, options, true),
buildExpression(group.value, options.reset(), true),
options options
); );
}; };
@@ -283,7 +280,7 @@ groupTypes.text = function(group, options) {
i--; i--;
} }
} }
return makeSpan(["mord", "text", newOptions.style.cls()], return makeSpan(["mord", "text"],
inner, newOptions); inner, newOptions);
}; };
@@ -311,43 +308,33 @@ groupTypes.supsub = function(group, options) {
return groupTypes[group.value.base.type](group, options); return groupTypes[group.value.base.type](group, options);
} }
const base = buildGroup(group.value.base, options.reset()); const base = buildGroup(group.value.base, options);
let supmid; let supm;
let submid; let subm;
let sup;
let sub;
const style = options.style; const style = options.style;
let newOptions; let newOptions;
// Rule 18a
let supShift = 0;
let subShift = 0;
if (group.value.sup) { if (group.value.sup) {
newOptions = options.withStyle(style.sup()); newOptions = options.havingStyle(style.sup());
sup = buildGroup(group.value.sup, newOptions); supm = buildGroup(group.value.sup, newOptions, options);
supmid = makeSpan([style.reset(), style.sup().cls()], if (!isCharacterBox(group.value.base)) {
[sup], newOptions); supShift = base.height - newOptions.style.metrics.supDrop
* newOptions.sizeMultiplier / options.sizeMultiplier;
}
} }
if (group.value.sub) { if (group.value.sub) {
newOptions = options.withStyle(style.sub()); newOptions = options.havingStyle(style.sub());
sub = buildGroup(group.value.sub, newOptions); subm = buildGroup(group.value.sub, newOptions, options);
submid = makeSpan([style.reset(), style.sub().cls()], if (!isCharacterBox(group.value.base)) {
[sub], newOptions); subShift = base.depth + newOptions.style.metrics.subDrop
} * newOptions.sizeMultiplier / options.sizeMultiplier;
}
// Rule 18a
let supShift;
let subShift;
if (isCharacterBox(group.value.base)) {
supShift = 0;
subShift = 0;
} else {
const supstyle = style.sup();
supShift = base.height - supstyle.metrics.supDrop
* supstyle.sizeMultiplier;
const substyle = style.sub();
subShift = base.depth + substyle.metrics.subDrop
* substyle.sizeMultiplier;
} }
// Rule 18c // Rule 18c
@@ -362,8 +349,7 @@ groupTypes.supsub = function(group, options) {
// scriptspace is a font-size-independent size, so scale it // scriptspace is a font-size-independent size, so scale it
// appropriately // appropriately
const multiplier = Style.TEXT.sizeMultiplier * const multiplier = options.sizeMultiplier;
style.sizeMultiplier;
const scriptspace = const scriptspace =
(0.5 / fontMetrics.metrics.ptPerEm) / multiplier + "em"; (0.5 / fontMetrics.metrics.ptPerEm) / multiplier + "em";
@@ -372,10 +358,10 @@ groupTypes.supsub = function(group, options) {
// Rule 18b // Rule 18b
subShift = Math.max( subShift = Math.max(
subShift, style.metrics.sub1, subShift, style.metrics.sub1,
sub.height - 0.8 * style.metrics.xHeight); subm.height - 0.8 * style.metrics.xHeight);
supsub = buildCommon.makeVList([ supsub = buildCommon.makeVList([
{type: "elem", elem: submid}, {type: "elem", elem: subm},
], "shift", subShift, options); ], "shift", subShift, options);
supsub.children[0].style.marginRight = scriptspace; supsub.children[0].style.marginRight = scriptspace;
@@ -389,25 +375,25 @@ groupTypes.supsub = function(group, 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,
sup.depth + 0.25 * style.metrics.xHeight); supm.depth + 0.25 * style.metrics.xHeight);
supsub = buildCommon.makeVList([ supsub = buildCommon.makeVList([
{type: "elem", elem: supmid}, {type: "elem", elem: supm},
], "shift", -supShift, options); ], "shift", -supShift, options);
supsub.children[0].style.marginRight = scriptspace; supsub.children[0].style.marginRight = scriptspace;
} else { } else {
supShift = Math.max( supShift = Math.max(
supShift, minSupShift, sup.depth + 0.25 * style.metrics.xHeight); supShift, minSupShift, supm.depth + 0.25 * style.metrics.xHeight);
subShift = Math.max(subShift, style.metrics.sub2); subShift = Math.max(subShift, style.metrics.sub2);
const ruleWidth = fontMetrics.metrics.defaultRuleThickness; const ruleWidth = fontMetrics.metrics.defaultRuleThickness;
// Rule 18e // Rule 18e
if ((supShift - sup.depth) - (sub.height - subShift) < if ((supShift - supm.depth) - (subm.height - subShift) <
4 * ruleWidth) { 4 * ruleWidth) {
subShift = 4 * ruleWidth - (supShift - sup.depth) + sub.height; subShift = 4 * ruleWidth - (supShift - supm.depth) + subm.height;
const psi = 0.8 * style.metrics.xHeight - (supShift - sup.depth); const psi = 0.8 * style.metrics.xHeight - (supShift - supm.depth);
if (psi > 0) { if (psi > 0) {
supShift += psi; supShift += psi;
subShift -= psi; subShift -= psi;
@@ -415,8 +401,8 @@ groupTypes.supsub = function(group, options) {
} }
supsub = buildCommon.makeVList([ supsub = buildCommon.makeVList([
{type: "elem", elem: submid, shift: subShift}, {type: "elem", elem: subm, shift: subShift},
{type: "elem", elem: supmid, shift: -supShift}, {type: "elem", elem: supm, shift: -supShift},
], "individualShift", null, options); ], "individualShift", null, options);
// See comment above about subscripts not being shifted // See comment above about subscripts not being shifted
@@ -450,23 +436,19 @@ groupTypes.genfrac = function(group, options) {
const dstyle = style.fracDen(); const dstyle = style.fracDen();
let newOptions; let newOptions;
newOptions = options.withStyle(nstyle); newOptions = options.havingStyle(nstyle);
const numer = buildGroup(group.value.numer, newOptions); const numerm = buildGroup(group.value.numer, newOptions, options);
const numerreset = makeSpan([style.reset(), nstyle.cls()],
[numer], newOptions);
newOptions = options.withStyle(dstyle); newOptions = options.havingStyle(dstyle);
const denom = buildGroup(group.value.denom, newOptions); const denomm = buildGroup(group.value.denom, newOptions, options);
const denomreset = makeSpan([style.reset(), dstyle.cls()],
[denom], newOptions);
let ruleWidth; let rule;
if (group.value.hasBarLine) { if (group.value.hasBarLine) {
ruleWidth = fontMetrics.metrics.defaultRuleThickness / rule = makeLineSpan("frac-line", options);
options.style.sizeMultiplier;
} else { } else {
ruleWidth = 0; rule = null;
} }
const ruleWidth = rule ? rule.height : 0;
// Rule 15b // Rule 15b
let numShift; let numShift;
@@ -495,53 +477,48 @@ groupTypes.genfrac = function(group, options) {
if (ruleWidth === 0) { if (ruleWidth === 0) {
// Rule 15c // Rule 15c
const candidateClearance = const candidateClearance =
(numShift - numer.depth) - (denom.height - denomShift); (numShift - numerm.depth) - (denomm.height - denomShift);
if (candidateClearance < clearance) { if (candidateClearance < clearance) {
numShift += 0.5 * (clearance - candidateClearance); numShift += 0.5 * (clearance - candidateClearance);
denomShift += 0.5 * (clearance - candidateClearance); denomShift += 0.5 * (clearance - candidateClearance);
} }
frac = buildCommon.makeVList([ frac = buildCommon.makeVList([
{type: "elem", elem: denomreset, shift: denomShift}, {type: "elem", elem: denomm, shift: denomShift},
{type: "elem", elem: numerreset, shift: -numShift}, {type: "elem", elem: numerm, shift: -numShift},
], "individualShift", null, options); ], "individualShift", null, options);
} else { } else {
// Rule 15d // Rule 15d
const axisHeight = style.metrics.axisHeight; const axisHeight = style.metrics.axisHeight;
if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth) < if ((numShift - numerm.depth) - (axisHeight + 0.5 * ruleWidth) <
clearance) { clearance) {
numShift += numShift +=
clearance - ((numShift - numer.depth) - clearance - ((numShift - numerm.depth) -
(axisHeight + 0.5 * ruleWidth)); (axisHeight + 0.5 * ruleWidth));
} }
if ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift) < if ((axisHeight - 0.5 * ruleWidth) - (denomm.height - denomShift) <
clearance) { clearance) {
denomShift += denomShift +=
clearance - ((axisHeight - 0.5 * ruleWidth) - clearance - ((axisHeight - 0.5 * ruleWidth) -
(denom.height - denomShift)); (denomm.height - denomShift));
} }
const 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;
const midShift = -(axisHeight - 0.5 * ruleWidth); const midShift = -(axisHeight - 0.5 * ruleWidth);
frac = buildCommon.makeVList([ frac = buildCommon.makeVList([
{type: "elem", elem: denomreset, shift: denomShift}, {type: "elem", elem: denomm, shift: denomShift},
{type: "elem", elem: mid, shift: midShift}, {type: "elem", elem: rule, shift: midShift},
{type: "elem", elem: numerreset, shift: -numShift}, {type: "elem", elem: numerm, 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 *= style.sizeMultiplier / options.style.sizeMultiplier; newOptions = options.havingStyle(style);
frac.depth *= style.sizeMultiplier / options.style.sizeMultiplier; frac.height *= newOptions.sizeMultiplier / options.sizeMultiplier;
frac.depth *= newOptions.sizeMultiplier / options.sizeMultiplier;
// Rule 15e // Rule 15e
let delimSize; let delimSize;
@@ -558,18 +535,18 @@ groupTypes.genfrac = function(group, options) {
} else { } else {
leftDelim = delimiter.customSizedDelim( leftDelim = delimiter.customSizedDelim(
group.value.leftDelim, delimSize, true, group.value.leftDelim, delimSize, true,
options.withStyle(style), group.mode, ["mopen"]); options.havingStyle(style), group.mode, ["mopen"]);
} }
if (group.value.rightDelim == null) { if (group.value.rightDelim == null) {
rightDelim = makeNullDelimiter(options, ["mclose"]); rightDelim = makeNullDelimiter(options, ["mclose"]);
} else { } else {
rightDelim = delimiter.customSizedDelim( rightDelim = delimiter.customSizedDelim(
group.value.rightDelim, delimSize, true, group.value.rightDelim, delimSize, true,
options.withStyle(style), group.mode, ["mclose"]); options.havingStyle(style), group.mode, ["mclose"]);
} }
return makeSpan( return makeSpan(
["mord", options.style.reset(), style.cls()], ["mord"].concat(newOptions.sizingClasses(options)),
[leftDelim, makeSpan(["mfrac"], [frac]), rightDelim], [leftDelim, makeSpan(["mfrac"], [frac]), rightDelim],
options); options);
}; };
@@ -770,18 +747,18 @@ groupTypes.spacing = function(group, options) {
groupTypes.llap = function(group, options) { groupTypes.llap = function(group, options) {
const inner = makeSpan( const inner = makeSpan(
["inner"], [buildGroup(group.value.body, options.reset())]); ["inner"], [buildGroup(group.value.body, options)]);
const fix = makeSpan(["fix"], []); const fix = makeSpan(["fix"], []);
return makeSpan( return makeSpan(
["mord", "llap", options.style.cls()], [inner, fix], options); ["mord", "llap"], [inner, fix], options);
}; };
groupTypes.rlap = function(group, options) { groupTypes.rlap = function(group, options) {
const inner = makeSpan( const inner = makeSpan(
["inner"], [buildGroup(group.value.body, options.reset())]); ["inner"], [buildGroup(group.value.body, options)]);
const fix = makeSpan(["fix"], []); const fix = makeSpan(["fix"], []);
return makeSpan( return makeSpan(
["mord", "rlap", options.style.cls()], [inner, fix], options); ["mord", "rlap"], [inner, fix], options);
}; };
groupTypes.op = function(group, options) { groupTypes.op = function(group, options) {
@@ -831,7 +808,7 @@ groupTypes.op = function(group, options) {
// don't actually apply this here, but instead it is used either in // don't actually apply this here, but instead it is used either in
// the vlist creation or separately when there are no limits. // the vlist creation or separately when there are no limits.
baseShift = (base.height - base.depth) / 2 - baseShift = (base.height - base.depth) / 2 -
style.metrics.axisHeight * style.sizeMultiplier; style.metrics.axisHeight * options.sizeMultiplier;
// The slant of the symbol is just its italic correction. // The slant of the symbol is just its italic correction.
slant = base.italic; slant = base.italic;
@@ -857,33 +834,29 @@ groupTypes.op = function(group, options) {
// in a new span so it is an inline, and works. // in a new span so it is an inline, and works.
base = makeSpan([], [base]); base = makeSpan([], [base]);
let supmid; let supm;
let supKern; let supKern;
let submid; let subm;
let subKern; let subKern;
let newOptions; let newOptions;
// We manually have to handle the superscripts and subscripts. This, // We manually have to handle the superscripts and subscripts. This,
// aside from the kern calculations, is copied from supsub. // aside from the kern calculations, is copied from supsub.
if (supGroup) { if (supGroup) {
newOptions = options.withStyle(style.sup()); newOptions = options.havingStyle(style.sup());
const sup = buildGroup(supGroup, newOptions); supm = buildGroup(supGroup, newOptions, options);
supmid = makeSpan([style.reset(), style.sup().cls()],
[sup], newOptions);
supKern = Math.max( supKern = Math.max(
fontMetrics.metrics.bigOpSpacing1, fontMetrics.metrics.bigOpSpacing1,
fontMetrics.metrics.bigOpSpacing3 - sup.depth); fontMetrics.metrics.bigOpSpacing3 - supm.depth);
} }
if (subGroup) { if (subGroup) {
newOptions = options.withStyle(style.sub()); newOptions = options.havingStyle(style.sub());
const sub = buildGroup(subGroup, newOptions); subm = buildGroup(subGroup, newOptions, options);
submid = makeSpan([style.reset(), style.sub().cls()],
[sub], newOptions);
subKern = Math.max( subKern = Math.max(
fontMetrics.metrics.bigOpSpacing2, fontMetrics.metrics.bigOpSpacing2,
fontMetrics.metrics.bigOpSpacing4 - sub.height); fontMetrics.metrics.bigOpSpacing4 - subm.height);
} }
// Build the final group as a vlist of the possible subscript, base, // Build the final group as a vlist of the possible subscript, base,
@@ -896,7 +869,7 @@ groupTypes.op = function(group, options) {
finalGroup = buildCommon.makeVList([ finalGroup = buildCommon.makeVList([
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, {type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
{type: "elem", elem: submid}, {type: "elem", elem: subm},
{type: "kern", size: subKern}, {type: "kern", size: subKern},
{type: "elem", elem: base}, {type: "elem", elem: base},
], "top", top, options); ], "top", top, options);
@@ -912,7 +885,7 @@ groupTypes.op = function(group, options) {
finalGroup = buildCommon.makeVList([ finalGroup = buildCommon.makeVList([
{type: "elem", elem: base}, {type: "elem", elem: base},
{type: "kern", size: supKern}, {type: "kern", size: supKern},
{type: "elem", elem: supmid}, {type: "elem", elem: supm},
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, {type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
], "bottom", bottom, options); ], "bottom", bottom, options);
@@ -925,17 +898,17 @@ groupTypes.op = function(group, options) {
return base; return base;
} else { } else {
bottom = fontMetrics.metrics.bigOpSpacing5 + bottom = fontMetrics.metrics.bigOpSpacing5 +
submid.height + submid.depth + subm.height + subm.depth +
subKern + subKern +
base.depth + baseShift; base.depth + baseShift;
finalGroup = buildCommon.makeVList([ finalGroup = buildCommon.makeVList([
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, {type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
{type: "elem", elem: submid}, {type: "elem", elem: subm},
{type: "kern", size: subKern}, {type: "kern", size: subKern},
{type: "elem", elem: base}, {type: "elem", elem: base},
{type: "kern", size: supKern}, {type: "kern", size: supKern},
{type: "elem", elem: supmid}, {type: "elem", elem: supm},
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, {type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
], "bottom", bottom, options); ], "bottom", bottom, options);
@@ -1034,29 +1007,33 @@ groupTypes.katex = function(group, options) {
["mord", "katex-logo"], [k, a, t, e, x], options); ["mord", "katex-logo"], [k, a, t, e, x], options);
}; };
const makeLineSpan = function(className, options) {
const baseOptions = options.havingBaseStyle();
const line = makeSpan(
[className].concat(baseOptions.sizingClasses(options)),
[], options);
line.height = fontMetrics.metrics.defaultRuleThickness /
options.sizeMultiplier;
line.maxFontSize = 1.0;
return line;
};
groupTypes.overline = function(group, options) { groupTypes.overline = function(group, options) {
// Overlines are handled in the TeXbook pg 443, Rule 9. // Overlines are handled in the TeXbook pg 443, Rule 9.
const style = options.style;
// Build the inner group in the cramped style. // Build the inner group in the cramped style.
const innerGroup = buildGroup(group.value.body, const innerGroup = buildGroup(group.value.body,
options.withStyle(style.cramp())); options.havingCrampedStyle());
const ruleWidth = fontMetrics.metrics.defaultRuleThickness /
style.sizeMultiplier;
// Create the line above the body // Create the line above the body
const line = makeSpan( const line = makeLineSpan("overline-line", options);
[style.reset(), Style.TEXT.cls(), "overline-line"]);
line.height = ruleWidth;
line.maxFontSize = 1.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: innerGroup}, {type: "elem", elem: innerGroup},
{type: "kern", size: 3 * ruleWidth}, {type: "kern", size: 3 * line.height},
{type: "elem", elem: line}, {type: "elem", elem: line},
{type: "kern", size: ruleWidth}, {type: "kern", size: line.height},
], "firstBaseline", null, options); ], "firstBaseline", null, options);
return makeSpan(["mord", "overline"], [vlist], options); return makeSpan(["mord", "overline"], [vlist], options);
@@ -1064,24 +1041,17 @@ groupTypes.overline = function(group, options) {
groupTypes.underline = function(group, options) { groupTypes.underline = function(group, options) {
// Underlines are handled in the TeXbook pg 443, Rule 10. // Underlines are handled in the TeXbook pg 443, Rule 10.
const style = options.style;
// Build the inner group. // Build the inner group.
const innerGroup = buildGroup(group.value.body, options); const innerGroup = buildGroup(group.value.body, options);
const ruleWidth = fontMetrics.metrics.defaultRuleThickness /
style.sizeMultiplier;
// Create the line above the body // Create the line above the body
const line = makeSpan([style.reset(), Style.TEXT.cls(), "underline-line"]); const line = makeLineSpan("underline-line", options);
line.height = ruleWidth;
line.maxFontSize = 1.0;
// 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: ruleWidth}, {type: "kern", size: line.height},
{type: "elem", elem: line}, {type: "elem", elem: line},
{type: "kern", size: 3 * ruleWidth}, {type: "kern", size: 3 * line.height},
{type: "elem", elem: innerGroup}, {type: "elem", elem: innerGroup},
], "top", innerGroup.height, options); ], "top", innerGroup.height, options);
@@ -1090,32 +1060,24 @@ groupTypes.underline = function(group, options) {
groupTypes.sqrt = function(group, options) { groupTypes.sqrt = function(group, options) {
// Square roots are handled in the TeXbook pg. 443, Rule 11. // Square roots are handled in the TeXbook pg. 443, Rule 11.
const style = options.style;
// First, we do the same steps as in overline to build the inner group // First, we do the same steps as in overline to build the inner group
// and line // and line
const inner = buildGroup( const inner = buildGroup(group.value.body, options.havingCrampedStyle());
group.value.body, options.withStyle(style.cramp()));
const ruleWidth = fontMetrics.metrics.defaultRuleThickness / const line = makeLineSpan("sqrt-line", options);
style.sizeMultiplier; const ruleWidth = line.height;
const line = makeSpan(
[style.reset(), Style.TEXT.cls(), "sqrt-line"], [],
options);
line.height = ruleWidth;
line.maxFontSize = 1.0;
let phi = ruleWidth; let phi = ruleWidth;
if (style.id < Style.TEXT.id) { if (options.style.id < Style.TEXT.id) {
phi = style.metrics.xHeight; phi = options.style.metrics.xHeight * options.sizeMultiplier;
} }
// Calculate the clearance between the body and line // Calculate the clearance between the body and line
let lineClearance = ruleWidth + phi / 4; let lineClearance = ruleWidth + phi / 4;
const innerHeight = (inner.height + inner.depth) * style.sizeMultiplier; const minDelimiterHeight = (inner.height + inner.depth +
const minDelimiterHeight = innerHeight + lineClearance + ruleWidth; lineClearance + ruleWidth) * options.sizeMultiplier;
// Create a \surd delimiter of the required minimum size // Create a \surd delimiter of the required minimum size
const delim = makeSpan(["sqrt-sign"], [ const delim = makeSpan(["sqrt-sign"], [
@@ -1161,12 +1123,8 @@ groupTypes.sqrt = function(group, options) {
// Handle the optional root index // Handle the optional root index
// The index is always in scriptscript style // The index is always in scriptscript style
const newOptions = options.withStyle(Style.SCRIPTSCRIPT); const newOptions = options.havingStyle(Style.SCRIPTSCRIPT);
const root = buildGroup(group.value.index, newOptions); const rootm = buildGroup(group.value.index, newOptions, options);
const rootWrap = makeSpan(
[style.reset(), Style.SCRIPTSCRIPT.cls()],
[root],
newOptions);
// Figure out the height and depth of the inner part // Figure out the height and depth of the inner part
const innerRootHeight = Math.max(delim.height, body.height); const innerRootHeight = Math.max(delim.height, body.height);
@@ -1178,7 +1136,7 @@ groupTypes.sqrt = function(group, options) {
// 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: rootWrap}], [{type: "elem", elem: rootm}],
"shift", -toShift, options); "shift", -toShift, 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
@@ -1189,35 +1147,37 @@ groupTypes.sqrt = function(group, options) {
} }
}; };
groupTypes.sizing = function(group, options) { function sizingGroup(value, options, baseOptions) {
// Handle sizing operators like \Huge. Real TeX doesn't actually allow const inner = buildExpression(value, options, false);
// these functions inside of math expressions, so we do some special const multiplier = options.sizeMultiplier / baseOptions.sizeMultiplier;
// handling.
const inner = buildExpression(group.value.value,
options.withSize(group.value.size), false);
// Compute the correct maxFontSize.
const style = options.style;
const fontSize = buildCommon.sizingMultiplier[group.value.size] *
style.sizeMultiplier;
// Add size-resetting classes to the inner list and set maxFontSize // Add size-resetting classes to the inner list and set maxFontSize
// manually. Handle nested size changes. // manually. Handle nested size changes.
for (let i = 0; i < inner.length; i++) { for (let i = 0; i < inner.length; i++) {
const pos = utils.indexOf(inner[i].classes, "sizing"); const pos = utils.indexOf(inner[i].classes, "sizing");
if (pos < 0) { if (pos < 0) {
inner[i].classes.push("sizing", "reset-" + options.size, Array.prototype.push.apply(inner[i].classes,
group.value.size, style.cls()); options.sizingClasses(baseOptions));
inner[i].maxFontSize = fontSize; } else if (inner[i].classes[pos + 1] === "reset-size" + options.size) {
} else if (inner[i].classes[pos + 1] === "reset-" + group.value.size) {
// This is a nested size change: e.g., inner[i] is the "b" in // This is a nested size change: e.g., inner[i] is the "b" in
// `\Huge a \small b`. Override the old size (the `reset-` class) // `\Huge a \small b`. Override the old size (the `reset-` class)
// but not the new size. // but not the new size.
inner[i].classes[pos + 1] = "reset-" + options.size; inner[i].classes[pos + 1] = "reset-size" + baseOptions.size;
} }
inner[i].height *= multiplier;
inner[i].depth *= multiplier;
} }
return buildCommon.makeFragment(inner); return buildCommon.makeFragment(inner);
}
groupTypes.sizing = function(group, options) {
// Handle sizing operators like \Huge. Real TeX doesn't actually allow
// these functions inside of math expressions, so we do some special
// handling.
const newOptions = options.havingSize(group.value.size);
return sizingGroup(group.value.value, newOptions, options);
}; };
groupTypes.styling = function(group, options) { groupTypes.styling = function(group, options) {
@@ -1232,25 +1192,8 @@ groupTypes.styling = function(group, options) {
}; };
const newStyle = styleMap[group.value.style]; const newStyle = styleMap[group.value.style];
const newOptions = options.withStyle(newStyle); const newOptions = options.havingStyle(newStyle);
return sizingGroup(group.value.value, newOptions, options);
// Build the inner expression in the new style.
const inner = buildExpression(
group.value.value, newOptions, false);
// Add style-resetting classes to the inner list. Handle nested changes.
for (let i = 0; i < inner.length; i++) {
const pos = utils.indexOf(inner[i].classes, newStyle.reset());
if (pos < 0) {
inner[i].classes.push(options.style.reset(), newStyle.cls());
} else {
// This is a nested style change, as `\textstyle a\scriptstyle b`.
// Only override the old style (the reset class).
inner[i].classes[pos] = options.style.reset();
}
}
return new buildCommon.makeFragment(inner);
}; };
groupTypes.font = function(group, options) { groupTypes.font = function(group, options) {
@@ -1275,7 +1218,7 @@ groupTypes.delimsizing = function(group, options) {
groupTypes.leftright = function(group, options) { groupTypes.leftright = function(group, options) {
// Build the inner expression // Build the inner expression
const inner = buildExpression(group.value.body, options.reset(), true); const inner = buildExpression(group.value.body, options, true);
let innerHeight = 0; let innerHeight = 0;
let innerDepth = 0; let innerDepth = 0;
@@ -1291,13 +1234,11 @@ groupTypes.leftright = function(group, options) {
} }
} }
const style = options.style;
// The size of delimiters is the same, regardless of what style we are // The size of delimiters is the same, regardless of what style we are
// in. Thus, to correctly calculate the size of delimiter we need around // in. Thus, to correctly calculate the size of delimiter we need around
// a group, we scale down the inner size based on the size. // a group, we scale down the inner size based on the size.
innerHeight *= style.sizeMultiplier; innerHeight *= options.sizeMultiplier;
innerDepth *= style.sizeMultiplier; innerDepth *= options.sizeMultiplier;
let leftDelim; let leftDelim;
if (group.value.left === ".") { if (group.value.left === ".") {
@@ -1343,8 +1284,7 @@ groupTypes.leftright = function(group, options) {
// Add it to the end of the expression. // Add it to the end of the expression.
inner.push(rightDelim); inner.push(rightDelim);
return makeSpan( return makeSpan(["minner"], inner, options);
["minner", style.cls()], inner, options);
}; };
groupTypes.middle = function(group, options) { groupTypes.middle = function(group, options) {
@@ -1376,9 +1316,9 @@ groupTypes.rule = function(group, options) {
// The sizes of rules are absolute, so make it larger if we are in a // The sizes of rules are absolute, so make it larger if we are in a
// smaller style. // smaller style.
shift /= style.sizeMultiplier; shift /= options.sizeMultiplier;
width /= style.sizeMultiplier; width /= options.sizeMultiplier;
height /= style.sizeMultiplier; height /= options.sizeMultiplier;
// Style the rule to the right size // Style the rule to the right size
rule.style.borderRightWidth = width + "em"; rule.style.borderRightWidth = width + "em";
@@ -1403,7 +1343,7 @@ groupTypes.kern = function(group, options) {
dimension = calculateSize(group.value.dimension, style); dimension = calculateSize(group.value.dimension, style);
} }
dimension /= style.sizeMultiplier; dimension /= options.sizeMultiplier;
rule.style.marginLeft = dimension + "em"; rule.style.marginLeft = dimension + "em";
@@ -1436,13 +1376,11 @@ groupTypes.accent = function(group, options) {
// Rerender the supsub group with its new base, and store that // Rerender the supsub group with its new base, and store that
// result. // result.
supsubGroup = buildGroup( supsubGroup = buildGroup(supsub, options);
supsub, options.reset());
} }
// Build the base group // Build the base group
const body = buildGroup( const body = buildGroup(base, options.havingCrampedStyle());
base, options.withStyle(style.cramp()));
// Does the accent need to shift for the skew of a character? // Does the accent need to shift for the skew of a character?
const mustShift = group.value.isShifty && isCharacterBox(base); const mustShift = group.value.isShifty && isCharacterBox(base);
@@ -1458,8 +1396,7 @@ groupTypes.accent = function(group, options) {
// innermost character. To do that, we find the innermost character: // innermost character. To do that, we find the innermost character:
const baseChar = getBaseElem(base); const baseChar = getBaseElem(base);
// Then, we render its group to get the symbol inside it // Then, we render its group to get the symbol inside it
const baseGroup = buildGroup( const baseGroup = buildGroup(baseChar, options.havingCrampedStyle());
baseChar, options.withStyle(style.cramp()));
// Finally, we pull the skew off of the symbol. // Finally, we pull the skew off of the symbol.
skew = baseGroup.skew; skew = baseGroup.skew;
// Note that we now throw away baseGroup, because the layers we // Note that we now throw away baseGroup, because the layers we
@@ -1543,28 +1480,23 @@ groupTypes.horizBrace = function(group, options) {
const hasSupSub = (group.type === "supsub"); const hasSupSub = (group.type === "supsub");
let supSubGroup; let supSubGroup;
let newOptions; let newOptions;
let supSubReset;
if (hasSupSub) { if (hasSupSub) {
// Ref: LaTeX source2e: }}}}\limits} // Ref: LaTeX source2e: }}}}\limits}
// i.e. LaTeX treats the brace similar to an op and passes it // i.e. LaTeX treats the brace similar to an op and passes it
// with \limits, so we need to assign supsub style. // with \limits, so we need to assign supsub style.
if (group.value.sup) { if (group.value.sup) {
newOptions = options.withStyle(style.sup()); newOptions = options.havingStyle(style.sup());
supSubGroup = buildGroup(group.value.sup, newOptions); supSubGroup = buildGroup(group.value.sup, newOptions, options);
supSubReset = makeSpan([style.reset(), style.sup().cls()],
[supSubGroup], newOptions);
} else { } else {
newOptions = options.withStyle(style.sub()); newOptions = options.havingStyle(style.sub());
supSubGroup = buildGroup(group.value.sub, newOptions); supSubGroup = buildGroup(group.value.sub, newOptions, options);
supSubReset = makeSpan([style.reset(), style.sub().cls()],
[supSubGroup], newOptions);
} }
group = group.value.base; group = group.value.base;
} }
// Build the base group // Build the base group
const body = buildGroup( const body = buildGroup(
group.value.base, options.withStyle(style.cramp())); group.value.base, options.havingStyle(style.cramp()));
// Create the stretchy element // Create the stretchy element
const braceBody = stretchy.svgSpan(group, options); const braceBody = stretchy.svgSpan(group, options);
@@ -1603,14 +1535,14 @@ groupTypes.horizBrace = function(group, options) {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList([
{type: "elem", elem: vSpan}, {type: "elem", elem: vSpan},
{type: "kern", size: 0.2}, {type: "kern", size: 0.2},
{type: "elem", elem: supSubReset}, {type: "elem", elem: supSubGroup},
], "firstBaseline", null, options); ], "firstBaseline", null, options);
} else { } else {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList([
{type: "elem", elem: supSubReset}, {type: "elem", elem: supSubGroup},
{type: "kern", size: 0.2}, {type: "kern", size: 0.2},
{type: "elem", elem: vSpan}, {type: "elem", elem: vSpan},
], "bottom", vSpan.depth + 0.2 + supSubReset.height, ], "bottom", vSpan.depth + 0.2 + supSubGroup.height,
options); options);
} }
} }
@@ -1638,10 +1570,10 @@ groupTypes.accentUnder = function(group, options) {
groupTypes.enclose = function(group, options) { groupTypes.enclose = function(group, options) {
// \cancel, \bcancel, \xcancel, \sout, \fbox // \cancel, \bcancel, \xcancel, \sout, \fbox
const inner = buildGroup(group.value.body, options.reset()); const inner = buildGroup(group.value.body, options);
const label = group.value.label.substr(1); const label = group.value.label.substr(1);
const scale = options.style.sizeMultiplier; const scale = options.sizeMultiplier;
let img; let img;
let pad = 0; let pad = 0;
let imgShift = 0; let imgShift = 0;
@@ -1695,19 +1627,14 @@ groupTypes.xArrow = function(group, options) {
// Build the argument groups in the appropriate style. // Build the argument groups in the appropriate style.
// Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}% // Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}%
let newOptions = options.withStyle(style.sup()); let newOptions = options.havingStyle(style.sup());
const upperGroup = buildGroup(group.value.body, newOptions); const upperGroup = buildGroup(group.value.body, newOptions, options);
const upperGroupWrap = makeSpan([style.reset(), style.sup().cls()],
[upperGroup], newOptions);
let lowerGroup; let lowerGroup;
let lowerGroupWrap;
if (group.value.below) { if (group.value.below) {
// Build the lower group // Build the lower group
newOptions = options.withStyle(style.sub()); newOptions = options.havingStyle(style.sub());
lowerGroup = buildGroup(group.value.below, newOptions); lowerGroup = buildGroup(group.value.below, newOptions, options);
lowerGroupWrap = makeSpan([style.reset(), style.sub().cls()],
[lowerGroup], newOptions);
} }
const arrowBody = stretchy.svgSpan(group, options); const arrowBody = stretchy.svgSpan(group, options);
@@ -1720,16 +1647,16 @@ groupTypes.xArrow = function(group, options) {
let vlist; let vlist;
if (group.value.below) { if (group.value.below) {
const lowerShift = -style.metrics.axisHeight const lowerShift = -style.metrics.axisHeight
+ lowerGroupWrap.height + arrowBody.height + lowerGroup.height + arrowBody.height
+ 0.111; + 0.111;
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList([
{type: "elem", elem: upperGroupWrap, shift: upperShift}, {type: "elem", elem: upperGroup, shift: upperShift},
{type: "elem", elem: arrowBody, shift: arrowShift}, {type: "elem", elem: arrowBody, shift: arrowShift},
{type: "elem", elem: lowerGroupWrap, shift: lowerShift}, {type: "elem", elem: lowerGroup, shift: lowerShift},
], "individualShift", null, options); ], "individualShift", null, options);
} else { } else {
vlist = buildCommon.makeVList([ vlist = buildCommon.makeVList([
{type: "elem", elem: upperGroupWrap, shift: upperShift}, {type: "elem", elem: upperGroup, shift: upperShift},
{type: "elem", elem: arrowBody, shift: arrowShift}, {type: "elem", elem: arrowBody, shift: arrowShift},
], "individualShift", null, options); ], "individualShift", null, options);
} }
@@ -1763,31 +1690,23 @@ groupTypes.mclass = function(group, options) {
* function for it. It also handles the interaction of size and style changes * function for it. It also handles the interaction of size and style changes
* between parents and children. * between parents and children.
*/ */
const buildGroup = function(group, options) { const buildGroup = function(group, options, baseOptions) {
if (!group) { if (!group) {
return makeSpan(); return makeSpan();
} }
if (groupTypes[group.type]) { if (groupTypes[group.type]) {
// Call the groupTypes function // Call the groupTypes function
const groupNode = groupTypes[group.type](group, options); let groupNode = groupTypes[group.type](group, options);
let multiplier;
// If the style changed between the parent and the current group,
// account for the size difference
if (options.style !== options.parentStyle) {
multiplier = options.style.sizeMultiplier /
options.parentStyle.sizeMultiplier;
groupNode.height *= multiplier;
groupNode.depth *= multiplier;
}
// If the size changed between the parent and the current group, account // If the size changed between the parent and the current group, account
// for that size difference. // for that size difference.
if (options.size !== options.parentSize) { if (baseOptions && options.size !== baseOptions.size) {
multiplier = buildCommon.sizingMultiplier[options.size] / groupNode = makeSpan(options.sizingClasses(baseOptions),
buildCommon.sizingMultiplier[options.parentSize]; [groupNode], options);
const multiplier = options.sizeMultiplier /
baseOptions.sizeMultiplier;
groupNode.height *= multiplier; groupNode.height *= multiplier;
groupNode.depth *= multiplier; groupNode.depth *= multiplier;
@@ -1811,7 +1730,7 @@ const buildHTML = function(tree, options) {
// Build the expression contained in the tree // Build the expression contained in the tree
const expression = buildExpression(tree, options, true); const expression = buildExpression(tree, options, true);
const body = makeSpan(["base", options.style.cls()], expression, options); const body = makeSpan(["base"], expression, options);
// Add struts, which ensure that the top of the HTML element falls at the // Add struts, which ensure that the top of the HTML element falls at the
// height of the expression, and the bottom of the HTML element falls at the // height of the expression, and the bottom of the HTML element falls at the

View File

@@ -480,7 +480,8 @@ groupTypes.styling = function(group, options) {
}; };
groupTypes.sizing = function(group, options) { groupTypes.sizing = function(group, options) {
const inner = buildExpression(group.value.value, options); const newOptions = options.havingSize(group.value.size);
const inner = buildExpression(group.value.value, newOptions);
const node = new mathMLTree.MathNode("mstyle", inner); const node = new mathMLTree.MathNode("mstyle", inner);
@@ -489,8 +490,7 @@ groupTypes.sizing = function(group, options) {
// in, so we can't reset the size to normal before changing it. Now // in, so we can't reset the size to normal before changing it. Now
// that we're passing an options parameter we should be able to fix // that we're passing an options parameter we should be able to fix
// this. // this.
node.setAttribute( node.setAttribute("mathsize", newOptions.sizeMultiplier + "em");
"mathsize", buildCommon.sizingMultiplier[group.value.size] + "em");
return node; return node;
}; };

View File

@@ -18,7 +18,6 @@ const buildTree = function(tree, expression, settings) {
// Setup the default options // Setup the default options
const options = new Options({ const options = new Options({
style: startStyle, style: startStyle,
size: "size5",
}); });
// `buildHTML` sometimes messes with the parse tree (like turning bins -> // `buildHTML` sometimes messes with the parse tree (like turning bins ->

View File

@@ -44,33 +44,36 @@ const getMetrics = function(symbol, font) {
} }
}; };
/**
* Builds a symbol in the given font size (note size is an integer)
*/
const mathrmSize = function(value, size, mode, options) {
return buildCommon.makeSymbol(value, "Size" + size + "-Regular",
mode, options);
};
/** /**
* Puts a delimiter span in a given style, and adds appropriate height, depth, * Puts a delimiter span in a given style, and adds appropriate height, depth,
* and maxFontSizes. * and maxFontSizes.
*/ */
const styleWrap = function(delim, toStyle, options, classes) { const styleWrap = function(delim, toStyle, options, classes) {
classes = classes || []; const newOptions = options.havingBaseStyle(toStyle);
const span = makeSpan( const span = makeSpan(
classes.concat(["style-wrap", options.style.reset(), toStyle.cls()]), (classes || []).concat(newOptions.sizingClasses(options)),
[delim], options); [delim], options);
const multiplier = toStyle.sizeMultiplier / options.style.sizeMultiplier; span.height *= newOptions.sizeMultiplier / options.sizeMultiplier;
span.depth *= newOptions.sizeMultiplier / options.sizeMultiplier;
span.height *= multiplier; span.maxFontSize = newOptions.sizeMultiplier;
span.depth *= multiplier;
span.maxFontSize = toStyle.sizeMultiplier;
return span; return span;
}; };
const centerSpan = function(span, options, style) {
const newOptions = options.havingBaseStyle(style);
const shift =
(1 - options.sizeMultiplier / newOptions.sizeMultiplier) *
options.style.metrics.axisHeight;
span.classes.push("delimcenter");
span.style.top = shift + "em";
span.height -= shift;
span.depth += shift;
};
/** /**
* Makes a small delimiter. This is a delimiter that comes in the Main-Regular * Makes a small delimiter. This is a delimiter that comes in the Main-Regular
* font, but is restyled to either be in textstyle, scriptstyle, or * font, but is restyled to either be in textstyle, scriptstyle, or
@@ -78,42 +81,33 @@ const styleWrap = function(delim, toStyle, options, classes) {
*/ */
const makeSmallDelim = function(delim, style, center, options, mode, classes) { const makeSmallDelim = function(delim, style, center, options, mode, classes) {
const text = buildCommon.makeSymbol(delim, "Main-Regular", mode, options); const text = buildCommon.makeSymbol(delim, "Main-Regular", mode, options);
const span = styleWrap(text, style, options, classes); const span = styleWrap(text, style, options, classes);
if (center) { if (center) {
const shift = centerSpan(span, options, style);
(1 - options.style.sizeMultiplier / style.sizeMultiplier) *
options.style.metrics.axisHeight;
span.style.top = shift + "em";
span.height -= shift;
span.depth += shift;
} }
return span; return span;
}; };
/**
* Builds a symbol in the given font size (note size is an integer)
*/
const mathrmSize = function(value, size, mode, options) {
return buildCommon.makeSymbol(value, "Size" + size + "-Regular",
mode, options);
};
/** /**
* Makes a large delimiter. This is a delimiter that comes in the Size1, Size2, * Makes a large delimiter. This is a delimiter that comes in the Size1, Size2,
* Size3, or Size4 fonts. It is always rendered in textstyle. * Size3, or Size4 fonts. It is always rendered in textstyle.
*/ */
const makeLargeDelim = function(delim, size, center, options, mode, classes) { const makeLargeDelim = function(delim, size, center, options, mode, classes) {
const inner = mathrmSize(delim, size, mode, options); const inner = mathrmSize(delim, size, mode, options);
const span = styleWrap( const span = styleWrap(
makeSpan(["delimsizing", "size" + size], [inner], options), makeSpan(["delimsizing", "size" + size], [inner], options),
Style.TEXT, options, classes); Style.TEXT, options, classes);
if (center) { if (center) {
const shift = (1 - options.style.sizeMultiplier) * centerSpan(span, options, Style.TEXT);
options.style.metrics.axisHeight;
span.style.top = shift + "em";
span.height -= shift;
span.depth += shift;
} }
return span; return span;
}; };
@@ -283,7 +277,7 @@ const makeStackedDelim = function(delim, heightTotal, center, options, mode,
// centered around the axis in textstyle. // centered around the axis in textstyle.
let axisHeight = options.style.metrics.axisHeight; let axisHeight = options.style.metrics.axisHeight;
if (center) { if (center) {
axisHeight *= options.style.sizeMultiplier; axisHeight *= options.sizeMultiplier;
} }
// Calculate the depth // Calculate the depth
const depth = realHeightTotal / 2 - axisHeight; const depth = realHeightTotal / 2 - axisHeight;
@@ -317,10 +311,11 @@ const makeStackedDelim = function(delim, heightTotal, center, options, mode,
inners.push(makeInner(top, font, mode)); inners.push(makeInner(top, font, mode));
// Finally, build the vlist // Finally, build the vlist
const inner = buildCommon.makeVList(inners, "bottom", depth, options); const newOptions = options.havingBaseStyle(Style.TEXT);
const inner = buildCommon.makeVList(inners, "bottom", depth, newOptions);
return styleWrap( return styleWrap(
makeSpan(["delimsizing", "mult"], [inner], options), makeSpan(["delimsizing", "mult"], [inner], newOptions),
Style.TEXT, options, classes); Style.TEXT, options, classes);
}; };
@@ -455,7 +450,8 @@ const traverseSequence = function(delim, height, sequence, options) {
// account for the style change size. // account for the style change size.
if (sequence[i].type === "small") { if (sequence[i].type === "small") {
heightDepth *= sequence[i].style.sizeMultiplier; const newOptions = options.havingBaseStyle(sequence[i].style);
heightDepth *= newOptions.sizeMultiplier;
} }
// Check if the delimiter at this size works for the given height. // Check if the delimiter at this size works for the given height.
@@ -514,7 +510,7 @@ const makeLeftRightDelim = function(delim, height, depth, options, mode,
classes) { classes) {
// We always center \left/\right delimiters, so the axis is always shifted // We always center \left/\right delimiters, so the axis is always shifted
const axisHeight = const axisHeight =
options.style.metrics.axisHeight * options.style.sizeMultiplier; options.style.metrics.axisHeight * options.sizeMultiplier;
// Taken from TeX source, tex.web, function make_left_right // Taken from TeX source, tex.web, function make_left_right
const delimiterFactor = 901; const delimiterFactor = 901;

View File

@@ -232,22 +232,6 @@
& + .mop.mtight { margin-left: @thinspace; } & + .mop.mtight { margin-left: @thinspace; }
} }
.reset-textstyle.textstyle { font-size: 1em; }
.reset-textstyle.scriptstyle { font-size: 0.7em; }
.reset-textstyle.scriptscriptstyle { font-size: 0.5em; }
.reset-scriptstyle.textstyle { font-size: 1.42857em; }
.reset-scriptstyle.scriptstyle { font-size: 1em; }
.reset-scriptstyle.scriptscriptstyle { font-size: 0.71429em; }
.reset-scriptscriptstyle.textstyle { font-size: 2em; }
.reset-scriptscriptstyle.scriptstyle { font-size: 1.4em; }
.reset-scriptscriptstyle.scriptscriptstyle { font-size: 1em; }
.style-wrap {
position: relative;
}
.vlist { .vlist {
display: inline-block; display: inline-block;
@@ -448,15 +432,16 @@
display: inline-block; display: inline-block;
@size1: 0.5; @size1: 0.5;
@size2: 0.7; @size2: 0.6;
@size3: 0.8; @size3: 0.7;
@size4: 0.9; @size4: 0.8;
@size5: 1.0; @size5: 0.9;
@size6: 1.2; @size6: 1.0;
@size7: 1.44; @size7: 1.2;
@size8: 1.73; @size8: 1.44;
@size9: 2.07; @size9: 1.728;
@size10: 2.49; @size10: 2.074;
@size11: 2.488;
.generate-size-change(@from, @to) { .generate-size-change(@from, @to) {
&.reset-size@{from}.size@{to} { &.reset-size@{from}.size@{to} {
@@ -466,13 +451,13 @@
} }
} }
.generate-to-size-change(@from, @currTo) when (@currTo =< 10) { .generate-to-size-change(@from, @currTo) when (@currTo =< 11) {
.generate-size-change(@from, @currTo); .generate-size-change(@from, @currTo);
.generate-to-size-change(@from, (@currTo + 1)); .generate-to-size-change(@from, (@currTo + 1));
} }
.generate-from-size-change(@currFrom) when (@currFrom =< 10) { .generate-from-size-change(@currFrom) when (@currFrom =< 11) {
.generate-to-size-change(@currFrom, 1); .generate-to-size-change(@currFrom, 1);
.generate-from-size-change((@currFrom + 1)); .generate-from-size-change((@currFrom + 1));
@@ -502,6 +487,10 @@
width: @nulldelimiterspace; width: @nulldelimiterspace;
} }
.delimcenter {
position: relative;
}
.op-symbol { .op-symbol {
position: relative; position: relative;

View File

@@ -17,7 +17,7 @@ const Style = require("../src/Style");
const defaultSettings = new Settings({}); const defaultSettings = new Settings({});
const defaultOptions = new Options({ const defaultOptions = new Options({
style: Style.TEXT, style: Style.TEXT,
size: "size5", size: 5,
}); });
const _getBuilt = function(expr, settings) { const _getBuilt = function(expr, settings) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 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: 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.5 KiB

After

Width:  |  Height:  |  Size: 7.5 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: 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: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 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: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 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: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 10 KiB