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.
144
src/Options.js
@@ -5,33 +5,43 @@
|
||||
* `.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
|
||||
* of the current parse level. It also contains the style and size of the parent
|
||||
* parse level, so size changes can be handled efficiently.
|
||||
* This is the main options class. It contains the current style, size, color,
|
||||
* and font.
|
||||
*
|
||||
* Each of the `.with*` and `.reset` functions passes its current style and size
|
||||
* as the parentStyle and parentSize of the new options class, so parent
|
||||
* handling is taken care of automatically.
|
||||
* Options objects should not be modified. To create a new Options with
|
||||
* different properties, call a `.having*` method.
|
||||
*/
|
||||
function Options(data) {
|
||||
this.style = data.style;
|
||||
this.color = data.color;
|
||||
this.size = data.size;
|
||||
this.size = data.size || BASESIZE;
|
||||
this.textSize = data.textSize || this.size;
|
||||
this.phantom = data.phantom;
|
||||
this.font = data.font;
|
||||
|
||||
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;
|
||||
}
|
||||
this.sizeMultiplier = sizeMultipliers[this.size - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,9 +52,8 @@ Options.prototype.extend = function(extension) {
|
||||
const data = {
|
||||
style: this.style,
|
||||
size: this.size,
|
||||
textSize: this.textSize,
|
||||
color: this.color,
|
||||
parentStyle: this.style,
|
||||
parentSize: this.size,
|
||||
phantom: this.phantom,
|
||||
font: this.font,
|
||||
};
|
||||
@@ -58,22 +67,66 @@ Options.prototype.extend = function(extension) {
|
||||
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) {
|
||||
return this.extend({
|
||||
style: style,
|
||||
});
|
||||
Options.prototype.havingStyle = function(style) {
|
||||
if (this.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) {
|
||||
return this.extend({
|
||||
size: size,
|
||||
});
|
||||
Options.prototype.havingCrampedStyle = function() {
|
||||
return this.havingStyle(this.style.cramp());
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* used so that parent style and size changes are handled correctly.
|
||||
* Return the CSS sizing classes required to switch from enclosing options
|
||||
* `oldOptions` to `this`. Returns an array of classes.
|
||||
*/
|
||||
Options.prototype.reset = function() {
|
||||
return this.extend({});
|
||||
Options.prototype.sizingClasses = function(oldOptions) {
|
||||
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;
|
||||
|
@@ -390,8 +390,8 @@ Parser.prototype.parseAtom = function() {
|
||||
|
||||
// A list of the size-changing functions, for use in parseImplicitGroup
|
||||
const sizeFuncs = [
|
||||
"\\tiny", "\\scriptsize", "\\footnotesize", "\\small", "\\normalsize",
|
||||
"\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge",
|
||||
"\\tiny", "\\sixptsize", "\\scriptsize", "\\footnotesize", "\\small",
|
||||
"\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge",
|
||||
];
|
||||
|
||||
// A list of the style-changing functions, for use in parseImplicitGroup
|
||||
@@ -483,7 +483,7 @@ Parser.prototype.parseImplicitGroup = function() {
|
||||
const body = this.parseExpression(false);
|
||||
return new ParseNode("sizing", {
|
||||
// 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,
|
||||
}, this.mode);
|
||||
} else if (utils.contains(styleFuncs, func)) {
|
||||
|
53
src/Style.js
@@ -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 same for cramped and uncramped version of a style), a cramped flag, and a
|
||||
* size multiplier, which gives the size difference between a style and
|
||||
* textstyle.
|
||||
* the same for cramped and uncramped version of a style), and a cramped flag.
|
||||
*/
|
||||
function Style(id, size, multiplier, cramped) {
|
||||
function Style(id, size, cramped) {
|
||||
this.id = id;
|
||||
this.size = size;
|
||||
this.cramped = cramped;
|
||||
this.sizeMultiplier = multiplier;
|
||||
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() {
|
||||
return sizeNames[this.size] + (this.cramped ? " cramped" : " uncramped");
|
||||
};
|
||||
|
||||
/**
|
||||
* HTML Reset class name, like "reset-textstyle"
|
||||
*/
|
||||
Style.prototype.reset = function() {
|
||||
return resetNames[this.size];
|
||||
Style.prototype.text = function() {
|
||||
return styles[text[this.id]];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -103,32 +93,16 @@ const Sc = 5;
|
||||
const SS = 6;
|
||||
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
|
||||
const styles = [
|
||||
new Style(D, 0, 1.0, false),
|
||||
new Style(Dc, 0, 1.0, true),
|
||||
new Style(T, 1, 1.0, false),
|
||||
new Style(Tc, 1, 1.0, true),
|
||||
new Style(S, 2, 0.7, false),
|
||||
new Style(Sc, 2, 0.7, true),
|
||||
new Style(SS, 3, 0.5, false),
|
||||
new Style(SSc, 3, 0.5, true),
|
||||
new Style(D, 0, false),
|
||||
new Style(Dc, 0, true),
|
||||
new Style(T, 1, false),
|
||||
new Style(Tc, 1, true),
|
||||
new Style(S, 2, false),
|
||||
new Style(Sc, 2, true),
|
||||
new Style(SS, 3, false),
|
||||
new Style(SSc, 3, true),
|
||||
];
|
||||
|
||||
// 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 fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, 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
|
||||
// no more styles can be generated.
|
||||
|
@@ -63,6 +63,7 @@ const makeSymbol = function(value, fontFamily, mode, options, classes) {
|
||||
}
|
||||
|
||||
if (options) {
|
||||
symbolNode.maxFontSize = options.sizeMultiplier;
|
||||
if (options.style.isTight()) {
|
||||
symbolNode.classes.push("mtight");
|
||||
}
|
||||
@@ -239,11 +240,10 @@ const makeFragment = function(children) {
|
||||
*/
|
||||
const makeFontSizer = function(options, fontSize) {
|
||||
const fontSizeInner = makeSpan([], [new domTree.symbolNode("\u200b")]);
|
||||
fontSizeInner.style.fontSize =
|
||||
(fontSize / options.style.sizeMultiplier) + "em";
|
||||
fontSizeInner.style.fontSize = fontSize + "em";
|
||||
|
||||
const fontSizer = makeSpan(
|
||||
["fontsize-ensurer", "reset-" + options.size, "size5"],
|
||||
["fontsize-ensurer"].concat(options.baseSizingClasses()),
|
||||
[fontSizeInner]);
|
||||
|
||||
return fontSizer;
|
||||
@@ -378,20 +378,6 @@ const makeVList = function(children, positionType, positionData, options) {
|
||||
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
|
||||
// CSS class
|
||||
const spacingFunctions = {
|
||||
@@ -486,6 +472,5 @@ module.exports = {
|
||||
makeVList: makeVList,
|
||||
makeOrd: makeOrd,
|
||||
prependChildren: prependChildren,
|
||||
sizingMultiplier: sizingMultiplier,
|
||||
spacingFunctions: spacingFunctions,
|
||||
};
|
||||
|
407
src/buildHTML.js
@@ -216,10 +216,8 @@ const isCharacterBox = function(group) {
|
||||
};
|
||||
|
||||
const makeNullDelimiter = function(options, classes) {
|
||||
return makeSpan(classes.concat([
|
||||
"sizing", "reset-" + options.size, "size5",
|
||||
options.style.reset(), Style.TEXT.cls(),
|
||||
"nulldelimiter"]));
|
||||
const moreClasses = ["nulldelimiter"].concat(options.baseSizingClasses());
|
||||
return makeSpan(classes.concat(moreClasses));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -267,9 +265,8 @@ groupTypes.punct = function(group, options) {
|
||||
};
|
||||
|
||||
groupTypes.ordgroup = function(group, options) {
|
||||
return makeSpan(
|
||||
["mord", options.style.cls()],
|
||||
buildExpression(group.value, options.reset(), true),
|
||||
return makeSpan(["mord"],
|
||||
buildExpression(group.value, options, true),
|
||||
options
|
||||
);
|
||||
};
|
||||
@@ -283,7 +280,7 @@ groupTypes.text = function(group, options) {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return makeSpan(["mord", "text", newOptions.style.cls()],
|
||||
return makeSpan(["mord", "text"],
|
||||
inner, newOptions);
|
||||
};
|
||||
|
||||
@@ -311,43 +308,33 @@ groupTypes.supsub = function(group, options) {
|
||||
return groupTypes[group.value.base.type](group, options);
|
||||
}
|
||||
|
||||
const base = buildGroup(group.value.base, options.reset());
|
||||
let supmid;
|
||||
let submid;
|
||||
let sup;
|
||||
let sub;
|
||||
const base = buildGroup(group.value.base, options);
|
||||
let supm;
|
||||
let subm;
|
||||
|
||||
const style = options.style;
|
||||
let newOptions;
|
||||
|
||||
// Rule 18a
|
||||
let supShift = 0;
|
||||
let subShift = 0;
|
||||
|
||||
if (group.value.sup) {
|
||||
newOptions = options.withStyle(style.sup());
|
||||
sup = buildGroup(group.value.sup, newOptions);
|
||||
supmid = makeSpan([style.reset(), style.sup().cls()],
|
||||
[sup], newOptions);
|
||||
newOptions = options.havingStyle(style.sup());
|
||||
supm = buildGroup(group.value.sup, newOptions, options);
|
||||
if (!isCharacterBox(group.value.base)) {
|
||||
supShift = base.height - newOptions.style.metrics.supDrop
|
||||
* newOptions.sizeMultiplier / options.sizeMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
if (group.value.sub) {
|
||||
newOptions = options.withStyle(style.sub());
|
||||
sub = buildGroup(group.value.sub, newOptions);
|
||||
submid = makeSpan([style.reset(), style.sub().cls()],
|
||||
[sub], newOptions);
|
||||
}
|
||||
|
||||
// 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;
|
||||
newOptions = options.havingStyle(style.sub());
|
||||
subm = buildGroup(group.value.sub, newOptions, options);
|
||||
if (!isCharacterBox(group.value.base)) {
|
||||
subShift = base.depth + newOptions.style.metrics.subDrop
|
||||
* newOptions.sizeMultiplier / options.sizeMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 18c
|
||||
@@ -362,8 +349,7 @@ groupTypes.supsub = function(group, options) {
|
||||
|
||||
// scriptspace is a font-size-independent size, so scale it
|
||||
// appropriately
|
||||
const multiplier = Style.TEXT.sizeMultiplier *
|
||||
style.sizeMultiplier;
|
||||
const multiplier = options.sizeMultiplier;
|
||||
const scriptspace =
|
||||
(0.5 / fontMetrics.metrics.ptPerEm) / multiplier + "em";
|
||||
|
||||
@@ -372,10 +358,10 @@ groupTypes.supsub = function(group, options) {
|
||||
// Rule 18b
|
||||
subShift = Math.max(
|
||||
subShift, style.metrics.sub1,
|
||||
sub.height - 0.8 * style.metrics.xHeight);
|
||||
subm.height - 0.8 * style.metrics.xHeight);
|
||||
|
||||
supsub = buildCommon.makeVList([
|
||||
{type: "elem", elem: submid},
|
||||
{type: "elem", elem: subm},
|
||||
], "shift", subShift, options);
|
||||
|
||||
supsub.children[0].style.marginRight = scriptspace;
|
||||
@@ -389,25 +375,25 @@ groupTypes.supsub = function(group, options) {
|
||||
} else if (!group.value.sub) {
|
||||
// Rule 18c, d
|
||||
supShift = Math.max(supShift, minSupShift,
|
||||
sup.depth + 0.25 * style.metrics.xHeight);
|
||||
supm.depth + 0.25 * style.metrics.xHeight);
|
||||
|
||||
supsub = buildCommon.makeVList([
|
||||
{type: "elem", elem: supmid},
|
||||
{type: "elem", elem: supm},
|
||||
], "shift", -supShift, options);
|
||||
|
||||
supsub.children[0].style.marginRight = scriptspace;
|
||||
} else {
|
||||
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);
|
||||
|
||||
const ruleWidth = fontMetrics.metrics.defaultRuleThickness;
|
||||
|
||||
// Rule 18e
|
||||
if ((supShift - sup.depth) - (sub.height - subShift) <
|
||||
if ((supShift - supm.depth) - (subm.height - subShift) <
|
||||
4 * ruleWidth) {
|
||||
subShift = 4 * ruleWidth - (supShift - sup.depth) + sub.height;
|
||||
const psi = 0.8 * style.metrics.xHeight - (supShift - sup.depth);
|
||||
subShift = 4 * ruleWidth - (supShift - supm.depth) + subm.height;
|
||||
const psi = 0.8 * style.metrics.xHeight - (supShift - supm.depth);
|
||||
if (psi > 0) {
|
||||
supShift += psi;
|
||||
subShift -= psi;
|
||||
@@ -415,8 +401,8 @@ groupTypes.supsub = function(group, options) {
|
||||
}
|
||||
|
||||
supsub = buildCommon.makeVList([
|
||||
{type: "elem", elem: submid, shift: subShift},
|
||||
{type: "elem", elem: supmid, shift: -supShift},
|
||||
{type: "elem", elem: subm, shift: subShift},
|
||||
{type: "elem", elem: supm, shift: -supShift},
|
||||
], "individualShift", null, options);
|
||||
|
||||
// See comment above about subscripts not being shifted
|
||||
@@ -450,23 +436,19 @@ groupTypes.genfrac = function(group, options) {
|
||||
const dstyle = style.fracDen();
|
||||
let newOptions;
|
||||
|
||||
newOptions = options.withStyle(nstyle);
|
||||
const numer = buildGroup(group.value.numer, newOptions);
|
||||
const numerreset = makeSpan([style.reset(), nstyle.cls()],
|
||||
[numer], newOptions);
|
||||
newOptions = options.havingStyle(nstyle);
|
||||
const numerm = buildGroup(group.value.numer, newOptions, options);
|
||||
|
||||
newOptions = options.withStyle(dstyle);
|
||||
const denom = buildGroup(group.value.denom, newOptions);
|
||||
const denomreset = makeSpan([style.reset(), dstyle.cls()],
|
||||
[denom], newOptions);
|
||||
newOptions = options.havingStyle(dstyle);
|
||||
const denomm = buildGroup(group.value.denom, newOptions, options);
|
||||
|
||||
let ruleWidth;
|
||||
let rule;
|
||||
if (group.value.hasBarLine) {
|
||||
ruleWidth = fontMetrics.metrics.defaultRuleThickness /
|
||||
options.style.sizeMultiplier;
|
||||
rule = makeLineSpan("frac-line", options);
|
||||
} else {
|
||||
ruleWidth = 0;
|
||||
rule = null;
|
||||
}
|
||||
const ruleWidth = rule ? rule.height : 0;
|
||||
|
||||
// Rule 15b
|
||||
let numShift;
|
||||
@@ -495,53 +477,48 @@ groupTypes.genfrac = function(group, options) {
|
||||
if (ruleWidth === 0) {
|
||||
// Rule 15c
|
||||
const candidateClearance =
|
||||
(numShift - numer.depth) - (denom.height - denomShift);
|
||||
(numShift - numerm.depth) - (denomm.height - denomShift);
|
||||
if (candidateClearance < clearance) {
|
||||
numShift += 0.5 * (clearance - candidateClearance);
|
||||
denomShift += 0.5 * (clearance - candidateClearance);
|
||||
}
|
||||
|
||||
frac = buildCommon.makeVList([
|
||||
{type: "elem", elem: denomreset, shift: denomShift},
|
||||
{type: "elem", elem: numerreset, shift: -numShift},
|
||||
{type: "elem", elem: denomm, shift: denomShift},
|
||||
{type: "elem", elem: numerm, shift: -numShift},
|
||||
], "individualShift", null, options);
|
||||
} else {
|
||||
// Rule 15d
|
||||
const axisHeight = style.metrics.axisHeight;
|
||||
|
||||
if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth) <
|
||||
if ((numShift - numerm.depth) - (axisHeight + 0.5 * ruleWidth) <
|
||||
clearance) {
|
||||
numShift +=
|
||||
clearance - ((numShift - numer.depth) -
|
||||
clearance - ((numShift - numerm.depth) -
|
||||
(axisHeight + 0.5 * ruleWidth));
|
||||
}
|
||||
|
||||
if ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift) <
|
||||
if ((axisHeight - 0.5 * ruleWidth) - (denomm.height - denomShift) <
|
||||
clearance) {
|
||||
denomShift +=
|
||||
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);
|
||||
|
||||
frac = buildCommon.makeVList([
|
||||
{type: "elem", elem: denomreset, shift: denomShift},
|
||||
{type: "elem", elem: mid, shift: midShift},
|
||||
{type: "elem", elem: numerreset, shift: -numShift},
|
||||
{type: "elem", elem: denomm, shift: denomShift},
|
||||
{type: "elem", elem: rule, shift: midShift},
|
||||
{type: "elem", elem: numerm, shift: -numShift},
|
||||
], "individualShift", null, options);
|
||||
}
|
||||
|
||||
// Since we manually change the style sometimes (with \dfrac or \tfrac),
|
||||
// account for the possible size change here.
|
||||
frac.height *= style.sizeMultiplier / options.style.sizeMultiplier;
|
||||
frac.depth *= style.sizeMultiplier / options.style.sizeMultiplier;
|
||||
newOptions = options.havingStyle(style);
|
||||
frac.height *= newOptions.sizeMultiplier / options.sizeMultiplier;
|
||||
frac.depth *= newOptions.sizeMultiplier / options.sizeMultiplier;
|
||||
|
||||
// Rule 15e
|
||||
let delimSize;
|
||||
@@ -558,18 +535,18 @@ groupTypes.genfrac = function(group, options) {
|
||||
} else {
|
||||
leftDelim = delimiter.customSizedDelim(
|
||||
group.value.leftDelim, delimSize, true,
|
||||
options.withStyle(style), group.mode, ["mopen"]);
|
||||
options.havingStyle(style), group.mode, ["mopen"]);
|
||||
}
|
||||
if (group.value.rightDelim == null) {
|
||||
rightDelim = makeNullDelimiter(options, ["mclose"]);
|
||||
} else {
|
||||
rightDelim = delimiter.customSizedDelim(
|
||||
group.value.rightDelim, delimSize, true,
|
||||
options.withStyle(style), group.mode, ["mclose"]);
|
||||
options.havingStyle(style), group.mode, ["mclose"]);
|
||||
}
|
||||
|
||||
return makeSpan(
|
||||
["mord", options.style.reset(), style.cls()],
|
||||
["mord"].concat(newOptions.sizingClasses(options)),
|
||||
[leftDelim, makeSpan(["mfrac"], [frac]), rightDelim],
|
||||
options);
|
||||
};
|
||||
@@ -770,18 +747,18 @@ groupTypes.spacing = function(group, options) {
|
||||
|
||||
groupTypes.llap = function(group, options) {
|
||||
const inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value.body, options.reset())]);
|
||||
["inner"], [buildGroup(group.value.body, options)]);
|
||||
const fix = makeSpan(["fix"], []);
|
||||
return makeSpan(
|
||||
["mord", "llap", options.style.cls()], [inner, fix], options);
|
||||
["mord", "llap"], [inner, fix], options);
|
||||
};
|
||||
|
||||
groupTypes.rlap = function(group, options) {
|
||||
const inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value.body, options.reset())]);
|
||||
["inner"], [buildGroup(group.value.body, options)]);
|
||||
const fix = makeSpan(["fix"], []);
|
||||
return makeSpan(
|
||||
["mord", "rlap", options.style.cls()], [inner, fix], options);
|
||||
["mord", "rlap"], [inner, fix], 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
|
||||
// the vlist creation or separately when there are no limits.
|
||||
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.
|
||||
slant = base.italic;
|
||||
@@ -857,33 +834,29 @@ groupTypes.op = function(group, options) {
|
||||
// in a new span so it is an inline, and works.
|
||||
base = makeSpan([], [base]);
|
||||
|
||||
let supmid;
|
||||
let supm;
|
||||
let supKern;
|
||||
let submid;
|
||||
let subm;
|
||||
let subKern;
|
||||
let newOptions;
|
||||
// We manually have to handle the superscripts and subscripts. This,
|
||||
// aside from the kern calculations, is copied from supsub.
|
||||
if (supGroup) {
|
||||
newOptions = options.withStyle(style.sup());
|
||||
const sup = buildGroup(supGroup, newOptions);
|
||||
supmid = makeSpan([style.reset(), style.sup().cls()],
|
||||
[sup], newOptions);
|
||||
newOptions = options.havingStyle(style.sup());
|
||||
supm = buildGroup(supGroup, newOptions, options);
|
||||
|
||||
supKern = Math.max(
|
||||
fontMetrics.metrics.bigOpSpacing1,
|
||||
fontMetrics.metrics.bigOpSpacing3 - sup.depth);
|
||||
fontMetrics.metrics.bigOpSpacing3 - supm.depth);
|
||||
}
|
||||
|
||||
if (subGroup) {
|
||||
newOptions = options.withStyle(style.sub());
|
||||
const sub = buildGroup(subGroup, newOptions);
|
||||
submid = makeSpan([style.reset(), style.sub().cls()],
|
||||
[sub], newOptions);
|
||||
newOptions = options.havingStyle(style.sub());
|
||||
subm = buildGroup(subGroup, newOptions, options);
|
||||
|
||||
subKern = Math.max(
|
||||
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,
|
||||
@@ -896,7 +869,7 @@ groupTypes.op = function(group, options) {
|
||||
|
||||
finalGroup = buildCommon.makeVList([
|
||||
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
|
||||
{type: "elem", elem: submid},
|
||||
{type: "elem", elem: subm},
|
||||
{type: "kern", size: subKern},
|
||||
{type: "elem", elem: base},
|
||||
], "top", top, options);
|
||||
@@ -912,7 +885,7 @@ groupTypes.op = function(group, options) {
|
||||
finalGroup = buildCommon.makeVList([
|
||||
{type: "elem", elem: base},
|
||||
{type: "kern", size: supKern},
|
||||
{type: "elem", elem: supmid},
|
||||
{type: "elem", elem: supm},
|
||||
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
|
||||
], "bottom", bottom, options);
|
||||
|
||||
@@ -925,17 +898,17 @@ groupTypes.op = function(group, options) {
|
||||
return base;
|
||||
} else {
|
||||
bottom = fontMetrics.metrics.bigOpSpacing5 +
|
||||
submid.height + submid.depth +
|
||||
subm.height + subm.depth +
|
||||
subKern +
|
||||
base.depth + baseShift;
|
||||
|
||||
finalGroup = buildCommon.makeVList([
|
||||
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
|
||||
{type: "elem", elem: submid},
|
||||
{type: "elem", elem: subm},
|
||||
{type: "kern", size: subKern},
|
||||
{type: "elem", elem: base},
|
||||
{type: "kern", size: supKern},
|
||||
{type: "elem", elem: supmid},
|
||||
{type: "elem", elem: supm},
|
||||
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
|
||||
], "bottom", bottom, options);
|
||||
|
||||
@@ -1034,29 +1007,33 @@ groupTypes.katex = function(group, 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) {
|
||||
// Overlines are handled in the TeXbook pg 443, Rule 9.
|
||||
const style = options.style;
|
||||
|
||||
// Build the inner group in the cramped style.
|
||||
const innerGroup = buildGroup(group.value.body,
|
||||
options.withStyle(style.cramp()));
|
||||
|
||||
const ruleWidth = fontMetrics.metrics.defaultRuleThickness /
|
||||
style.sizeMultiplier;
|
||||
options.havingCrampedStyle());
|
||||
|
||||
// Create the line above the body
|
||||
const line = makeSpan(
|
||||
[style.reset(), Style.TEXT.cls(), "overline-line"]);
|
||||
line.height = ruleWidth;
|
||||
line.maxFontSize = 1.0;
|
||||
const line = makeLineSpan("overline-line", options);
|
||||
|
||||
// Generate the vlist, with the appropriate kerns
|
||||
const vlist = buildCommon.makeVList([
|
||||
{type: "elem", elem: innerGroup},
|
||||
{type: "kern", size: 3 * ruleWidth},
|
||||
{type: "kern", size: 3 * line.height},
|
||||
{type: "elem", elem: line},
|
||||
{type: "kern", size: ruleWidth},
|
||||
{type: "kern", size: line.height},
|
||||
], "firstBaseline", null, options);
|
||||
|
||||
return makeSpan(["mord", "overline"], [vlist], options);
|
||||
@@ -1064,24 +1041,17 @@ groupTypes.overline = function(group, options) {
|
||||
|
||||
groupTypes.underline = function(group, options) {
|
||||
// Underlines are handled in the TeXbook pg 443, Rule 10.
|
||||
const style = options.style;
|
||||
|
||||
// Build the inner group.
|
||||
const innerGroup = buildGroup(group.value.body, options);
|
||||
|
||||
const ruleWidth = fontMetrics.metrics.defaultRuleThickness /
|
||||
style.sizeMultiplier;
|
||||
|
||||
// Create the line above the body
|
||||
const line = makeSpan([style.reset(), Style.TEXT.cls(), "underline-line"]);
|
||||
line.height = ruleWidth;
|
||||
line.maxFontSize = 1.0;
|
||||
const line = makeLineSpan("underline-line", options);
|
||||
|
||||
// Generate the vlist, with the appropriate kerns
|
||||
const vlist = buildCommon.makeVList([
|
||||
{type: "kern", size: ruleWidth},
|
||||
{type: "kern", size: line.height},
|
||||
{type: "elem", elem: line},
|
||||
{type: "kern", size: 3 * ruleWidth},
|
||||
{type: "kern", size: 3 * line.height},
|
||||
{type: "elem", elem: innerGroup},
|
||||
], "top", innerGroup.height, options);
|
||||
|
||||
@@ -1090,32 +1060,24 @@ groupTypes.underline = function(group, options) {
|
||||
|
||||
groupTypes.sqrt = function(group, options) {
|
||||
// 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
|
||||
// and line
|
||||
const inner = buildGroup(
|
||||
group.value.body, options.withStyle(style.cramp()));
|
||||
const inner = buildGroup(group.value.body, options.havingCrampedStyle());
|
||||
|
||||
const ruleWidth = fontMetrics.metrics.defaultRuleThickness /
|
||||
style.sizeMultiplier;
|
||||
|
||||
const line = makeSpan(
|
||||
[style.reset(), Style.TEXT.cls(), "sqrt-line"], [],
|
||||
options);
|
||||
line.height = ruleWidth;
|
||||
line.maxFontSize = 1.0;
|
||||
const line = makeLineSpan("sqrt-line", options);
|
||||
const ruleWidth = line.height;
|
||||
|
||||
let phi = ruleWidth;
|
||||
if (style.id < Style.TEXT.id) {
|
||||
phi = style.metrics.xHeight;
|
||||
if (options.style.id < Style.TEXT.id) {
|
||||
phi = options.style.metrics.xHeight * options.sizeMultiplier;
|
||||
}
|
||||
|
||||
// Calculate the clearance between the body and line
|
||||
let lineClearance = ruleWidth + phi / 4;
|
||||
|
||||
const innerHeight = (inner.height + inner.depth) * style.sizeMultiplier;
|
||||
const minDelimiterHeight = innerHeight + lineClearance + ruleWidth;
|
||||
const minDelimiterHeight = (inner.height + inner.depth +
|
||||
lineClearance + ruleWidth) * options.sizeMultiplier;
|
||||
|
||||
// Create a \surd delimiter of the required minimum size
|
||||
const delim = makeSpan(["sqrt-sign"], [
|
||||
@@ -1161,12 +1123,8 @@ groupTypes.sqrt = function(group, options) {
|
||||
// Handle the optional root index
|
||||
|
||||
// The index is always in scriptscript style
|
||||
const newOptions = options.withStyle(Style.SCRIPTSCRIPT);
|
||||
const root = buildGroup(group.value.index, newOptions);
|
||||
const rootWrap = makeSpan(
|
||||
[style.reset(), Style.SCRIPTSCRIPT.cls()],
|
||||
[root],
|
||||
newOptions);
|
||||
const newOptions = options.havingStyle(Style.SCRIPTSCRIPT);
|
||||
const rootm = buildGroup(group.value.index, newOptions, options);
|
||||
|
||||
// Figure out the height and depth of the inner part
|
||||
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
|
||||
const rootVList = buildCommon.makeVList(
|
||||
[{type: "elem", elem: rootWrap}],
|
||||
[{type: "elem", elem: rootm}],
|
||||
"shift", -toShift, options);
|
||||
// Add a class surrounding it so we can add on the appropriate
|
||||
// kerning
|
||||
@@ -1189,35 +1147,37 @@ groupTypes.sqrt = function(group, options) {
|
||||
}
|
||||
};
|
||||
|
||||
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 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;
|
||||
function sizingGroup(value, options, baseOptions) {
|
||||
const inner = buildExpression(value, options, false);
|
||||
const multiplier = options.sizeMultiplier / baseOptions.sizeMultiplier;
|
||||
|
||||
// Add size-resetting classes to the inner list and set maxFontSize
|
||||
// manually. Handle nested size changes.
|
||||
for (let i = 0; i < inner.length; i++) {
|
||||
const pos = utils.indexOf(inner[i].classes, "sizing");
|
||||
if (pos < 0) {
|
||||
inner[i].classes.push("sizing", "reset-" + options.size,
|
||||
group.value.size, style.cls());
|
||||
inner[i].maxFontSize = fontSize;
|
||||
} else if (inner[i].classes[pos + 1] === "reset-" + group.value.size) {
|
||||
Array.prototype.push.apply(inner[i].classes,
|
||||
options.sizingClasses(baseOptions));
|
||||
} else if (inner[i].classes[pos + 1] === "reset-size" + options.size) {
|
||||
// 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)
|
||||
// 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1232,25 +1192,8 @@ groupTypes.styling = function(group, options) {
|
||||
};
|
||||
|
||||
const newStyle = styleMap[group.value.style];
|
||||
const newOptions = options.withStyle(newStyle);
|
||||
|
||||
// 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);
|
||||
const newOptions = options.havingStyle(newStyle);
|
||||
return sizingGroup(group.value.value, newOptions, options);
|
||||
};
|
||||
|
||||
groupTypes.font = function(group, options) {
|
||||
@@ -1275,7 +1218,7 @@ groupTypes.delimsizing = function(group, options) {
|
||||
|
||||
groupTypes.leftright = function(group, options) {
|
||||
// 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 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
|
||||
// in. Thus, to correctly calculate the size of delimiter we need around
|
||||
// a group, we scale down the inner size based on the size.
|
||||
innerHeight *= style.sizeMultiplier;
|
||||
innerDepth *= style.sizeMultiplier;
|
||||
innerHeight *= options.sizeMultiplier;
|
||||
innerDepth *= options.sizeMultiplier;
|
||||
|
||||
let leftDelim;
|
||||
if (group.value.left === ".") {
|
||||
@@ -1343,8 +1284,7 @@ groupTypes.leftright = function(group, options) {
|
||||
// Add it to the end of the expression.
|
||||
inner.push(rightDelim);
|
||||
|
||||
return makeSpan(
|
||||
["minner", style.cls()], inner, options);
|
||||
return makeSpan(["minner"], inner, 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
|
||||
// smaller style.
|
||||
shift /= style.sizeMultiplier;
|
||||
width /= style.sizeMultiplier;
|
||||
height /= style.sizeMultiplier;
|
||||
shift /= options.sizeMultiplier;
|
||||
width /= options.sizeMultiplier;
|
||||
height /= options.sizeMultiplier;
|
||||
|
||||
// Style the rule to the right size
|
||||
rule.style.borderRightWidth = width + "em";
|
||||
@@ -1403,7 +1343,7 @@ groupTypes.kern = function(group, options) {
|
||||
dimension = calculateSize(group.value.dimension, style);
|
||||
}
|
||||
|
||||
dimension /= style.sizeMultiplier;
|
||||
dimension /= options.sizeMultiplier;
|
||||
|
||||
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
|
||||
// result.
|
||||
supsubGroup = buildGroup(
|
||||
supsub, options.reset());
|
||||
supsubGroup = buildGroup(supsub, options);
|
||||
}
|
||||
|
||||
// Build the base group
|
||||
const body = buildGroup(
|
||||
base, options.withStyle(style.cramp()));
|
||||
const body = buildGroup(base, options.havingCrampedStyle());
|
||||
|
||||
// Does the accent need to shift for the skew of a character?
|
||||
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:
|
||||
const baseChar = getBaseElem(base);
|
||||
// Then, we render its group to get the symbol inside it
|
||||
const baseGroup = buildGroup(
|
||||
baseChar, options.withStyle(style.cramp()));
|
||||
const baseGroup = buildGroup(baseChar, options.havingCrampedStyle());
|
||||
// Finally, we pull the skew off of the symbol.
|
||||
skew = baseGroup.skew;
|
||||
// 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");
|
||||
let supSubGroup;
|
||||
let newOptions;
|
||||
let supSubReset;
|
||||
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.withStyle(style.sup());
|
||||
supSubGroup = buildGroup(group.value.sup, newOptions);
|
||||
supSubReset = makeSpan([style.reset(), style.sup().cls()],
|
||||
[supSubGroup], newOptions);
|
||||
newOptions = options.havingStyle(style.sup());
|
||||
supSubGroup = buildGroup(group.value.sup, newOptions, options);
|
||||
} else {
|
||||
newOptions = options.withStyle(style.sub());
|
||||
supSubGroup = buildGroup(group.value.sub, newOptions);
|
||||
supSubReset = makeSpan([style.reset(), style.sub().cls()],
|
||||
[supSubGroup], newOptions);
|
||||
newOptions = options.havingStyle(style.sub());
|
||||
supSubGroup = buildGroup(group.value.sub, newOptions, options);
|
||||
}
|
||||
group = group.value.base;
|
||||
}
|
||||
|
||||
// Build the base group
|
||||
const body = buildGroup(
|
||||
group.value.base, options.withStyle(style.cramp()));
|
||||
group.value.base, options.havingStyle(style.cramp()));
|
||||
|
||||
// Create the stretchy element
|
||||
const braceBody = stretchy.svgSpan(group, options);
|
||||
@@ -1603,14 +1535,14 @@ groupTypes.horizBrace = function(group, options) {
|
||||
vlist = buildCommon.makeVList([
|
||||
{type: "elem", elem: vSpan},
|
||||
{type: "kern", size: 0.2},
|
||||
{type: "elem", elem: supSubReset},
|
||||
{type: "elem", elem: supSubGroup},
|
||||
], "firstBaseline", null, options);
|
||||
} else {
|
||||
vlist = buildCommon.makeVList([
|
||||
{type: "elem", elem: supSubReset},
|
||||
{type: "elem", elem: supSubGroup},
|
||||
{type: "kern", size: 0.2},
|
||||
{type: "elem", elem: vSpan},
|
||||
], "bottom", vSpan.depth + 0.2 + supSubReset.height,
|
||||
], "bottom", vSpan.depth + 0.2 + supSubGroup.height,
|
||||
options);
|
||||
}
|
||||
}
|
||||
@@ -1638,10 +1570,10 @@ groupTypes.accentUnder = function(group, options) {
|
||||
|
||||
groupTypes.enclose = function(group, options) {
|
||||
// \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 scale = options.style.sizeMultiplier;
|
||||
const scale = options.sizeMultiplier;
|
||||
let img;
|
||||
let pad = 0;
|
||||
let imgShift = 0;
|
||||
@@ -1695,19 +1627,14 @@ groupTypes.xArrow = function(group, options) {
|
||||
// Build the argument groups in the appropriate style.
|
||||
// Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}%
|
||||
|
||||
let newOptions = options.withStyle(style.sup());
|
||||
const upperGroup = buildGroup(group.value.body, newOptions);
|
||||
const upperGroupWrap = makeSpan([style.reset(), style.sup().cls()],
|
||||
[upperGroup], newOptions);
|
||||
let newOptions = options.havingStyle(style.sup());
|
||||
const upperGroup = buildGroup(group.value.body, newOptions, options);
|
||||
|
||||
let lowerGroup;
|
||||
let lowerGroupWrap;
|
||||
if (group.value.below) {
|
||||
// Build the lower group
|
||||
newOptions = options.withStyle(style.sub());
|
||||
lowerGroup = buildGroup(group.value.below, newOptions);
|
||||
lowerGroupWrap = makeSpan([style.reset(), style.sub().cls()],
|
||||
[lowerGroup], newOptions);
|
||||
newOptions = options.havingStyle(style.sub());
|
||||
lowerGroup = buildGroup(group.value.below, newOptions, options);
|
||||
}
|
||||
|
||||
const arrowBody = stretchy.svgSpan(group, options);
|
||||
@@ -1720,16 +1647,16 @@ groupTypes.xArrow = function(group, options) {
|
||||
let vlist;
|
||||
if (group.value.below) {
|
||||
const lowerShift = -style.metrics.axisHeight
|
||||
+ lowerGroupWrap.height + arrowBody.height
|
||||
+ lowerGroup.height + arrowBody.height
|
||||
+ 0.111;
|
||||
vlist = buildCommon.makeVList([
|
||||
{type: "elem", elem: upperGroupWrap, shift: upperShift},
|
||||
{type: "elem", elem: upperGroup, shift: upperShift},
|
||||
{type: "elem", elem: arrowBody, shift: arrowShift},
|
||||
{type: "elem", elem: lowerGroupWrap, shift: lowerShift},
|
||||
{type: "elem", elem: lowerGroup, shift: lowerShift},
|
||||
], "individualShift", null, options);
|
||||
} else {
|
||||
vlist = buildCommon.makeVList([
|
||||
{type: "elem", elem: upperGroupWrap, shift: upperShift},
|
||||
{type: "elem", elem: upperGroup, shift: upperShift},
|
||||
{type: "elem", elem: arrowBody, shift: arrowShift},
|
||||
], "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
|
||||
* between parents and children.
|
||||
*/
|
||||
const buildGroup = function(group, options) {
|
||||
const buildGroup = function(group, options, baseOptions) {
|
||||
if (!group) {
|
||||
return makeSpan();
|
||||
}
|
||||
|
||||
if (groupTypes[group.type]) {
|
||||
// Call the groupTypes function
|
||||
const 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;
|
||||
}
|
||||
let groupNode = groupTypes[group.type](group, options);
|
||||
|
||||
// If the size changed between the parent and the current group, account
|
||||
// for that size difference.
|
||||
if (options.size !== options.parentSize) {
|
||||
multiplier = buildCommon.sizingMultiplier[options.size] /
|
||||
buildCommon.sizingMultiplier[options.parentSize];
|
||||
if (baseOptions && options.size !== baseOptions.size) {
|
||||
groupNode = makeSpan(options.sizingClasses(baseOptions),
|
||||
[groupNode], options);
|
||||
|
||||
const multiplier = options.sizeMultiplier /
|
||||
baseOptions.sizeMultiplier;
|
||||
|
||||
groupNode.height *= multiplier;
|
||||
groupNode.depth *= multiplier;
|
||||
@@ -1811,7 +1730,7 @@ const buildHTML = function(tree, options) {
|
||||
|
||||
// Build the expression contained in the tree
|
||||
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
|
||||
// height of the expression, and the bottom of the HTML element falls at the
|
||||
|
@@ -480,7 +480,8 @@ groupTypes.styling = 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);
|
||||
|
||||
@@ -489,8 +490,7 @@ groupTypes.sizing = function(group, options) {
|
||||
// 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
|
||||
// this.
|
||||
node.setAttribute(
|
||||
"mathsize", buildCommon.sizingMultiplier[group.value.size] + "em");
|
||||
node.setAttribute("mathsize", newOptions.sizeMultiplier + "em");
|
||||
|
||||
return node;
|
||||
};
|
||||
|
@@ -18,7 +18,6 @@ const buildTree = function(tree, expression, settings) {
|
||||
// Setup the default options
|
||||
const options = new Options({
|
||||
style: startStyle,
|
||||
size: "size5",
|
||||
});
|
||||
|
||||
// `buildHTML` sometimes messes with the parse tree (like turning bins ->
|
||||
|
@@ -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,
|
||||
* and maxFontSizes.
|
||||
*/
|
||||
const styleWrap = function(delim, toStyle, options, classes) {
|
||||
classes = classes || [];
|
||||
const newOptions = options.havingBaseStyle(toStyle);
|
||||
|
||||
const span = makeSpan(
|
||||
classes.concat(["style-wrap", options.style.reset(), toStyle.cls()]),
|
||||
(classes || []).concat(newOptions.sizingClasses(options)),
|
||||
[delim], options);
|
||||
|
||||
const multiplier = toStyle.sizeMultiplier / options.style.sizeMultiplier;
|
||||
|
||||
span.height *= multiplier;
|
||||
span.depth *= multiplier;
|
||||
span.maxFontSize = toStyle.sizeMultiplier;
|
||||
span.height *= newOptions.sizeMultiplier / options.sizeMultiplier;
|
||||
span.depth *= newOptions.sizeMultiplier / options.sizeMultiplier;
|
||||
span.maxFontSize = newOptions.sizeMultiplier;
|
||||
|
||||
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
|
||||
* 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 text = buildCommon.makeSymbol(delim, "Main-Regular", mode, options);
|
||||
|
||||
const span = styleWrap(text, style, options, classes);
|
||||
|
||||
if (center) {
|
||||
const shift =
|
||||
(1 - options.style.sizeMultiplier / style.sizeMultiplier) *
|
||||
options.style.metrics.axisHeight;
|
||||
|
||||
span.style.top = shift + "em";
|
||||
span.height -= shift;
|
||||
span.depth += shift;
|
||||
centerSpan(span, options, style);
|
||||
}
|
||||
|
||||
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,
|
||||
* Size3, or Size4 fonts. It is always rendered in textstyle.
|
||||
*/
|
||||
const makeLargeDelim = function(delim, size, center, options, mode, classes) {
|
||||
const inner = mathrmSize(delim, size, mode, options);
|
||||
|
||||
const span = styleWrap(
|
||||
makeSpan(["delimsizing", "size" + size], [inner], options),
|
||||
Style.TEXT, options, classes);
|
||||
|
||||
if (center) {
|
||||
const shift = (1 - options.style.sizeMultiplier) *
|
||||
options.style.metrics.axisHeight;
|
||||
|
||||
span.style.top = shift + "em";
|
||||
span.height -= shift;
|
||||
span.depth += shift;
|
||||
centerSpan(span, options, Style.TEXT);
|
||||
}
|
||||
|
||||
return span;
|
||||
};
|
||||
|
||||
@@ -283,7 +277,7 @@ const makeStackedDelim = function(delim, heightTotal, center, options, mode,
|
||||
// centered around the axis in textstyle.
|
||||
let axisHeight = options.style.metrics.axisHeight;
|
||||
if (center) {
|
||||
axisHeight *= options.style.sizeMultiplier;
|
||||
axisHeight *= options.sizeMultiplier;
|
||||
}
|
||||
// Calculate the depth
|
||||
const depth = realHeightTotal / 2 - axisHeight;
|
||||
@@ -317,10 +311,11 @@ const makeStackedDelim = function(delim, heightTotal, center, options, mode,
|
||||
inners.push(makeInner(top, font, mode));
|
||||
|
||||
// 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(
|
||||
makeSpan(["delimsizing", "mult"], [inner], options),
|
||||
makeSpan(["delimsizing", "mult"], [inner], newOptions),
|
||||
Style.TEXT, options, classes);
|
||||
};
|
||||
|
||||
@@ -455,7 +450,8 @@ const traverseSequence = function(delim, height, sequence, options) {
|
||||
// account for the style change size.
|
||||
|
||||
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.
|
||||
@@ -514,7 +510,7 @@ const makeLeftRightDelim = function(delim, height, depth, options, mode,
|
||||
classes) {
|
||||
// We always center \left/\right delimiters, so the axis is always shifted
|
||||
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
|
||||
const delimiterFactor = 901;
|
||||
|
@@ -232,22 +232,6 @@
|
||||
& + .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 {
|
||||
display: inline-block;
|
||||
|
||||
@@ -448,15 +432,16 @@
|
||||
display: inline-block;
|
||||
|
||||
@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;
|
||||
@size2: 0.6;
|
||||
@size3: 0.7;
|
||||
@size4: 0.8;
|
||||
@size5: 0.9;
|
||||
@size6: 1.0;
|
||||
@size7: 1.2;
|
||||
@size8: 1.44;
|
||||
@size9: 1.728;
|
||||
@size10: 2.074;
|
||||
@size11: 2.488;
|
||||
|
||||
.generate-size-change(@from, @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-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-from-size-change((@currFrom + 1));
|
||||
@@ -502,6 +487,10 @@
|
||||
width: @nulldelimiterspace;
|
||||
}
|
||||
|
||||
.delimcenter {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.op-symbol {
|
||||
position: relative;
|
||||
|
||||
|
@@ -17,7 +17,7 @@ const Style = require("../src/Style");
|
||||
const defaultSettings = new Settings({});
|
||||
const defaultOptions = new Options({
|
||||
style: Style.TEXT,
|
||||
size: "size5",
|
||||
size: 5,
|
||||
});
|
||||
|
||||
const _getBuilt = function(expr, settings) {
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 10 KiB |