
Summary: Add support for all of the other operators, including the ones with symbols and limits. This also fixes the bug where subscripts were shifted the same amount as subscripts. To accomplish this, the domTree.textNode has been repurposed into symbolNode which is no longer an actual text node, but instead represents an element with a single symbol in it. This lets us access properties like the italic correction of a symbol in a reasonable manner without having to recursively look through children of spans. Depends on D13082 Fixes #8 Test Plan: - Make sure tests work - Make sure huxley screenshots didn't change much, and new screenshot looks good Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D13122
788 lines
25 KiB
JavaScript
788 lines
25 KiB
JavaScript
var Options = require("./Options");
|
|
var ParseError = require("./ParseError");
|
|
var Style = require("./Style");
|
|
|
|
var buildCommon = require("./buildCommon");
|
|
var delimiter = require("./delimiter");
|
|
var domTree = require("./domTree");
|
|
var fontMetrics = require("./fontMetrics");
|
|
var parseTree = require("./parseTree");
|
|
var symbols = require("./symbols");
|
|
var utils = require("./utils");
|
|
|
|
var makeSpan = buildCommon.makeSpan;
|
|
|
|
var buildExpression = function(expression, options, prev) {
|
|
var groups = [];
|
|
for (var i = 0; i < expression.length; i++) {
|
|
var group = expression[i];
|
|
groups.push(buildGroup(group, options, prev));
|
|
prev = group;
|
|
}
|
|
return groups;
|
|
};
|
|
|
|
var groupToType = {
|
|
mathord: "mord",
|
|
textord: "mord",
|
|
bin: "mbin",
|
|
rel: "mrel",
|
|
text: "mord",
|
|
open: "mopen",
|
|
close: "mclose",
|
|
frac: "minner",
|
|
spacing: "mord",
|
|
punct: "mpunct",
|
|
ordgroup: "mord",
|
|
op: "mop",
|
|
katex: "mord",
|
|
overline: "mord",
|
|
rule: "mord",
|
|
leftright: "minner",
|
|
sqrt: "mord"
|
|
};
|
|
|
|
var getTypeOfGroup = function(group) {
|
|
if (group == null) {
|
|
// Like when typesetting $^3$
|
|
return groupToType.mathord;
|
|
} else if (group.type === "supsub") {
|
|
return getTypeOfGroup(group.value.base);
|
|
} else if (group.type === "llap" || group.type === "rlap") {
|
|
return getTypeOfGroup(group.value);
|
|
} else if (group.type === "color") {
|
|
return getTypeOfGroup(group.value.value);
|
|
} else if (group.type === "sizing") {
|
|
return getTypeOfGroup(group.value.value);
|
|
} else if (group.type === "styling") {
|
|
return getTypeOfGroup(group.value.value);
|
|
} else if (group.type === "delimsizing") {
|
|
return groupToType[group.value.delimType];
|
|
} else {
|
|
return groupToType[group.type];
|
|
}
|
|
};
|
|
|
|
var isCharacterBox = function(group) {
|
|
if (group == null) {
|
|
return false;
|
|
} else if (group.type === "mathord" ||
|
|
group.type === "textord" ||
|
|
group.type === "bin" ||
|
|
group.type === "rel" ||
|
|
group.type === "open" ||
|
|
group.type === "close" ||
|
|
group.type === "punct") {
|
|
return true;
|
|
} else if (group.type === "ordgroup") {
|
|
return group.value.length === 1 && isCharacterBox(group.value[0]);
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
var shouldHandleSupSub = function(group, options) {
|
|
if (group == null) {
|
|
return false;
|
|
} else if (group.type === "op") {
|
|
return group.value.limits && options.style.id === Style.DISPLAY.id;
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
var groupTypes = {
|
|
mathord: function(group, options, prev) {
|
|
return buildCommon.mathit(
|
|
group.value, group.mode, options.getColor(), ["mord"]);
|
|
},
|
|
|
|
textord: function(group, options, prev) {
|
|
return buildCommon.mathrm(
|
|
group.value, group.mode, options.getColor(), ["mord"]);
|
|
},
|
|
|
|
bin: function(group, options, prev) {
|
|
var className = "mbin";
|
|
var prevAtom = prev;
|
|
while (prevAtom && prevAtom.type == "color") {
|
|
var atoms = prevAtom.value.value;
|
|
prevAtom = atoms[atoms.length - 1];
|
|
}
|
|
if (!prev || utils.contains(["mbin", "mopen", "mrel", "mop", "mpunct"],
|
|
getTypeOfGroup(prevAtom))) {
|
|
group.type = "ord";
|
|
className = "mord";
|
|
}
|
|
|
|
return buildCommon.mathrm(
|
|
group.value, group.mode, options.getColor(), [className]);
|
|
},
|
|
|
|
rel: function(group, options, prev) {
|
|
return buildCommon.mathrm(
|
|
group.value, group.mode, options.getColor(), ["mrel"]);
|
|
},
|
|
|
|
text: function(group, options, prev) {
|
|
return makeSpan(["text", "mord", options.style.cls()],
|
|
buildExpression(group.value.body, options.reset()));
|
|
},
|
|
|
|
supsub: function(group, options, prev) {
|
|
var baseGroup = group.value.base;
|
|
|
|
if (shouldHandleSupSub(group.value.base, options)) {
|
|
return groupTypes[group.value.base.type](group, options, prev);
|
|
}
|
|
|
|
var base = buildGroup(group.value.base, options.reset());
|
|
|
|
if (group.value.sup) {
|
|
var sup = buildGroup(group.value.sup,
|
|
options.withStyle(options.style.sup()));
|
|
var supmid = makeSpan(
|
|
[options.style.reset(), options.style.sup().cls()], [sup]);
|
|
}
|
|
|
|
if (group.value.sub) {
|
|
var sub = buildGroup(group.value.sub,
|
|
options.withStyle(options.style.sub()));
|
|
var submid = makeSpan(
|
|
[options.style.reset(), options.style.sub().cls()], [sub]);
|
|
}
|
|
|
|
var u, v;
|
|
if (isCharacterBox(group.value.base)) {
|
|
u = 0;
|
|
v = 0;
|
|
} else {
|
|
u = base.height - fontMetrics.metrics.supDrop;
|
|
v = base.depth + fontMetrics.metrics.subDrop;
|
|
}
|
|
|
|
var p;
|
|
if (options.style === Style.DISPLAY) {
|
|
p = fontMetrics.metrics.sup1;
|
|
} else if (options.style.cramped) {
|
|
p = fontMetrics.metrics.sup3;
|
|
} else {
|
|
p = fontMetrics.metrics.sup2;
|
|
}
|
|
|
|
var multiplier = Style.TEXT.sizeMultiplier *
|
|
options.style.sizeMultiplier;
|
|
var scriptspace =
|
|
(0.5 / fontMetrics.metrics.ptPerEm) / multiplier + "em";
|
|
|
|
var supsub;
|
|
|
|
if (!group.value.sup) {
|
|
v = Math.max(v, fontMetrics.metrics.sub1,
|
|
sub.height - 0.8 * fontMetrics.metrics.xHeight);
|
|
|
|
supsub = buildCommon.makeVList([
|
|
{type: "elem", elem: submid}
|
|
], "shift", v, options);
|
|
|
|
supsub.children[0].style.marginRight = scriptspace;
|
|
|
|
if (base instanceof domTree.symbolNode) {
|
|
supsub.children[0].style.marginLeft = -base.italic + "em";
|
|
}
|
|
} else if (!group.value.sub) {
|
|
u = Math.max(u, p,
|
|
sup.depth + 0.25 * fontMetrics.metrics.xHeight);
|
|
|
|
supsub = buildCommon.makeVList([
|
|
{type: "elem", elem: supmid}
|
|
], "shift", -u, options);
|
|
|
|
supsub.children[0].style.marginRight = scriptspace;
|
|
} else {
|
|
u = Math.max(u, p,
|
|
sup.depth + 0.25 * fontMetrics.metrics.xHeight);
|
|
v = Math.max(v, fontMetrics.metrics.sub2);
|
|
|
|
var theta = fontMetrics.metrics.defaultRuleThickness;
|
|
|
|
if ((u - sup.depth) - (sub.height - v) < 4 * theta) {
|
|
v = 4 * theta - (u - sup.depth) + sub.height;
|
|
var psi = 0.8 * fontMetrics.metrics.xHeight - (u - sup.depth);
|
|
if (psi > 0) {
|
|
u += psi;
|
|
v -= psi;
|
|
}
|
|
}
|
|
|
|
supsub = buildCommon.makeVList([
|
|
{type: "elem", elem: submid, shift: v},
|
|
{type: "elem", elem: supmid, shift: -u}
|
|
], "individualShift", null, options);
|
|
|
|
if (base instanceof domTree.symbolNode) {
|
|
supsub.children[1].style.marginLeft = base.italic + "em";
|
|
base.italic = 0;
|
|
}
|
|
|
|
supsub.children[0].style.marginRight = scriptspace;
|
|
supsub.children[1].style.marginRight = scriptspace;
|
|
}
|
|
|
|
return makeSpan([getTypeOfGroup(group.value.base)],
|
|
[base, supsub]);
|
|
},
|
|
|
|
open: function(group, options, prev) {
|
|
return buildCommon.mathrm(
|
|
group.value, group.mode, options.getColor(), ["mopen"]);
|
|
},
|
|
|
|
close: function(group, options, prev) {
|
|
return buildCommon.mathrm(
|
|
group.value, group.mode, options.getColor(), ["mclose"]);
|
|
},
|
|
|
|
frac: function(group, options, prev) {
|
|
var fstyle = options.style;
|
|
if (group.value.size === "dfrac") {
|
|
fstyle = Style.DISPLAY;
|
|
} else if (group.value.size === "tfrac") {
|
|
fstyle = Style.TEXT;
|
|
}
|
|
|
|
var nstyle = fstyle.fracNum();
|
|
var dstyle = fstyle.fracDen();
|
|
|
|
var numer = buildGroup(group.value.numer, options.withStyle(nstyle));
|
|
var numerreset = makeSpan([fstyle.reset(), nstyle.cls()], [numer]);
|
|
|
|
var denom = buildGroup(group.value.denom, options.withStyle(dstyle));
|
|
var denomreset = makeSpan([fstyle.reset(), dstyle.cls()], [denom])
|
|
|
|
var theta = fontMetrics.metrics.defaultRuleThickness / options.style.sizeMultiplier;
|
|
|
|
var mid = makeSpan([options.style.reset(), Style.TEXT.cls(), "frac-line"]);
|
|
mid.height = theta;
|
|
|
|
var u, v, phi;
|
|
if (fstyle.size === Style.DISPLAY.size) {
|
|
u = fontMetrics.metrics.num1;
|
|
v = fontMetrics.metrics.denom1;
|
|
phi = 3 * theta;
|
|
} else {
|
|
u = fontMetrics.metrics.num2;
|
|
v = fontMetrics.metrics.denom2;
|
|
phi = theta;
|
|
}
|
|
|
|
var a = fontMetrics.metrics.axisHeight;
|
|
|
|
if ((u - numer.depth) - (a + 0.5 * theta) < phi) {
|
|
u += phi - ((u - numer.depth) - (a + 0.5 * theta));
|
|
}
|
|
|
|
if ((a - 0.5 * theta) - (denom.height - v) < phi) {
|
|
v += phi - ((a - 0.5 * theta) - (denom.height - v));
|
|
}
|
|
|
|
var midShift = -(a - 0.5 * theta);
|
|
|
|
var frac = buildCommon.makeVList([
|
|
{type: "elem", elem: denomreset, shift: v},
|
|
{type: "elem", elem: mid, shift: midShift},
|
|
{type: "elem", elem: numerreset, shift: -u}
|
|
], "individualShift", null, options);
|
|
|
|
frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
|
frac.depth *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
|
|
|
return makeSpan(
|
|
["minner", "mfrac", options.style.reset(), fstyle.cls()],
|
|
[frac], options.getColor());
|
|
},
|
|
|
|
color: function(group, options, prev) {
|
|
var elements = buildExpression(
|
|
group.value.value,
|
|
options.withColor(group.value.color),
|
|
prev
|
|
);
|
|
|
|
return new buildCommon.makeFragment(elements);
|
|
},
|
|
|
|
spacing: function(group, options, prev) {
|
|
if (group.value === "\\ " || group.value === "\\space" ||
|
|
group.value === " " || group.value === "~") {
|
|
return makeSpan(
|
|
["mord", "mspace"],
|
|
[buildCommon.mathrm(group.value, group.mode)]
|
|
);
|
|
} else {
|
|
var spacingClassMap = {
|
|
"\\qquad": "qquad",
|
|
"\\quad": "quad",
|
|
"\\enspace": "enspace",
|
|
"\\;": "thickspace",
|
|
"\\:": "mediumspace",
|
|
"\\,": "thinspace",
|
|
"\\!": "negativethinspace"
|
|
};
|
|
|
|
return makeSpan(
|
|
["mord", "mspace", spacingClassMap[group.value]]);
|
|
}
|
|
},
|
|
|
|
llap: function(group, options, prev) {
|
|
var inner = makeSpan(
|
|
["inner"], [buildGroup(group.value.body, options.reset())]);
|
|
var fix = makeSpan(["fix"], []);
|
|
return makeSpan(
|
|
["llap", options.style.cls()], [inner, fix]);
|
|
},
|
|
|
|
rlap: function(group, options, prev) {
|
|
var inner = makeSpan(
|
|
["inner"], [buildGroup(group.value.body, options.reset())]);
|
|
var fix = makeSpan(["fix"], []);
|
|
return makeSpan(
|
|
["rlap", options.style.cls()], [inner, fix]);
|
|
},
|
|
|
|
punct: function(group, options, prev) {
|
|
return buildCommon.mathrm(
|
|
group.value, group.mode, options.getColor(), ["mpunct"]);
|
|
},
|
|
|
|
ordgroup: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mord", options.style.cls()],
|
|
buildExpression(group.value, options.reset())
|
|
);
|
|
},
|
|
|
|
op: function(group, options, prev) {
|
|
var supGroup;
|
|
var subGroup;
|
|
var hasLimits = false;
|
|
if (group.type === "supsub" ) {
|
|
supGroup = group.value.sup;
|
|
subGroup = group.value.sub;
|
|
group = group.value.base;
|
|
hasLimits = true;
|
|
}
|
|
|
|
// Most operators have a large successor symbol, but these don't.
|
|
var noSuccessor = [
|
|
"\\smallint"
|
|
];
|
|
|
|
var large = false;
|
|
|
|
if (options.style.id === Style.DISPLAY.id &&
|
|
group.value.symbol &&
|
|
!utils.contains(noSuccessor, group.value.body)) {
|
|
|
|
// Make symbols larger in displaystyle, except for smallint
|
|
large = true;
|
|
}
|
|
|
|
var base;
|
|
var baseShift = 0;
|
|
var delta = 0;
|
|
if (group.value.symbol) {
|
|
var style = large ? "Size2-Regular" : "Size1-Regular";
|
|
base = buildCommon.makeSymbol(
|
|
group.value.body, style, "math", options.getColor(),
|
|
["op-symbol", large ? "large-op" : "small-op", "mop"]);
|
|
|
|
baseShift = (base.height - base.depth) / 2 -
|
|
fontMetrics.metrics.axisHeight *
|
|
options.style.sizeMultiplier;
|
|
delta = base.italic;
|
|
} else {
|
|
var output = [];
|
|
for (var i = 1; i < group.value.body.length; i++) {
|
|
output.push(buildCommon.mathrm(group.value.body[i], group.mode));
|
|
}
|
|
base = makeSpan(["mop"], output, options.getColor());
|
|
}
|
|
|
|
if (hasLimits) {
|
|
if (supGroup) {
|
|
var sup = buildGroup(supGroup,
|
|
options.withStyle(options.style.sup()));
|
|
var supmid = makeSpan(
|
|
[options.style.reset(), options.style.sup().cls()], [sup]);
|
|
|
|
var supKern = Math.max(
|
|
fontMetrics.metrics.bigOpSpacing1,
|
|
fontMetrics.metrics.bigOpSpacing3 - sup.depth);
|
|
}
|
|
|
|
if (subGroup) {
|
|
var sub = buildGroup(subGroup,
|
|
options.withStyle(options.style.sub()));
|
|
var submid = makeSpan(
|
|
[options.style.reset(), options.style.sub().cls()], [sub]);
|
|
|
|
var subKern = Math.max(
|
|
fontMetrics.metrics.bigOpSpacing2,
|
|
fontMetrics.metrics.bigOpSpacing4 - sub.height);
|
|
}
|
|
|
|
var finalGroup;
|
|
if (!supGroup) {
|
|
var top = base.height - baseShift;
|
|
|
|
finalGroup = buildCommon.makeVList([
|
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
|
|
{type: "elem", elem: submid},
|
|
{type: "kern", size: subKern},
|
|
{type: "elem", elem: base}
|
|
], "top", top, options);
|
|
|
|
finalGroup.children[0].style.marginLeft = -delta + "em";
|
|
} else if (!subGroup) {
|
|
var bottom = base.depth + baseShift;
|
|
|
|
finalGroup = buildCommon.makeVList([
|
|
{type: "elem", elem: base},
|
|
{type: "kern", size: supKern},
|
|
{type: "elem", elem: supmid},
|
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}
|
|
], "bottom", bottom, options);
|
|
|
|
finalGroup.children[1].style.marginLeft = delta + "em";
|
|
} else if (!supGroup && !subGroup) {
|
|
return base;
|
|
} else {
|
|
var bottom = fontMetrics.metrics.bigOpSpacing5 +
|
|
submid.height + submid.depth +
|
|
subKern +
|
|
base.depth + baseShift;
|
|
|
|
finalGroup = buildCommon.makeVList([
|
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5},
|
|
{type: "elem", elem: submid},
|
|
{type: "kern", size: subKern},
|
|
{type: "elem", elem: base},
|
|
{type: "kern", size: supKern},
|
|
{type: "elem", elem: supmid},
|
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}
|
|
], "bottom", bottom, options);
|
|
|
|
finalGroup.children[0].style.marginLeft = -delta + "em";
|
|
finalGroup.children[2].style.marginLeft = delta + "em";
|
|
}
|
|
|
|
return makeSpan(["mop", "op-limits"], [finalGroup]);
|
|
} else {
|
|
if (group.value.symbol) {
|
|
base.style.top = baseShift + "em";
|
|
}
|
|
|
|
return base;
|
|
}
|
|
},
|
|
|
|
katex: function(group, options, prev) {
|
|
var k = makeSpan(
|
|
["k"], [buildCommon.mathrm("K", group.mode)]);
|
|
var a = makeSpan(
|
|
["a"], [buildCommon.mathrm("A", group.mode)]);
|
|
|
|
a.height = (a.height + 0.2) * 0.75;
|
|
a.depth = (a.height - 0.2) * 0.75;
|
|
|
|
var t = makeSpan(
|
|
["t"], [buildCommon.mathrm("T", group.mode)]);
|
|
var e = makeSpan(
|
|
["e"], [buildCommon.mathrm("E", group.mode)]);
|
|
|
|
e.height = (e.height - 0.2155);
|
|
e.depth = (e.depth + 0.2155);
|
|
|
|
var x = makeSpan(
|
|
["x"], [buildCommon.mathrm("X", group.mode)]);
|
|
|
|
return makeSpan(
|
|
["katex-logo"], [k, a, t, e, x], options.getColor());
|
|
},
|
|
|
|
sqrt: function(group, options, prev) {
|
|
var inner = buildGroup(group.value.body,
|
|
options.withStyle(options.style.cramp()));
|
|
|
|
var theta = fontMetrics.metrics.defaultRuleThickness /
|
|
options.style.sizeMultiplier;
|
|
|
|
var line = makeSpan(
|
|
[options.style.reset(), Style.TEXT.cls(), "sqrt-line"], [],
|
|
options.getColor());
|
|
line.height = theta;
|
|
line.maxFontSize = 1.0;
|
|
|
|
var phi = theta;
|
|
if (options.style.id < Style.TEXT.id) {
|
|
phi = fontMetrics.metrics.xHeight;
|
|
}
|
|
|
|
var psi = theta + phi / 4;
|
|
|
|
var innerHeight =
|
|
(inner.height + inner.depth) * options.style.sizeMultiplier;
|
|
var minDelimiterHeight = innerHeight + psi + theta;
|
|
|
|
var delim = makeSpan(["sqrt-sign"], [
|
|
delimiter.customSizedDelim("\\surd", minDelimiterHeight,
|
|
false, options, group.mode)],
|
|
options.getColor());
|
|
|
|
var delimDepth = (delim.height + delim.depth) - theta;
|
|
|
|
if (delimDepth > inner.height + inner.depth + psi) {
|
|
psi = (psi + delimDepth - inner.height - inner.depth) / 2;
|
|
}
|
|
|
|
delimShift = -(inner.height + psi + theta) + delim.height;
|
|
delim.style.top = delimShift + "em";
|
|
delim.height -= delimShift;
|
|
delim.depth += delimShift;
|
|
|
|
// We add a special case here, because even when `inner` is empty, we
|
|
// still get a line. So, we use a simple heuristic to decide if we
|
|
// should omit the body entirely. (note this doesn't work for something
|
|
// like `\sqrt{\rlap{x}}`, but if someone is doing that they deserve for
|
|
// it not to work.
|
|
var body;
|
|
if (inner.height === 0 && inner.depth === 0) {
|
|
body = makeSpan();
|
|
} else {
|
|
body = buildCommon.makeVList([
|
|
{type: "elem", elem: inner},
|
|
{type: "kern", size: psi},
|
|
{type: "elem", elem: line},
|
|
{type: "kern", size: theta}
|
|
], "firstBaseline", null, options);
|
|
}
|
|
|
|
return makeSpan(["sqrt", "mord"], [delim, body]);
|
|
},
|
|
|
|
overline: function(group, options, prev) {
|
|
var innerGroup = buildGroup(group.value.body,
|
|
options.withStyle(options.style.cramp()));
|
|
|
|
var theta = fontMetrics.metrics.defaultRuleThickness /
|
|
options.style.sizeMultiplier;
|
|
|
|
var line = makeSpan(
|
|
[options.style.reset(), Style.TEXT.cls(), "overline-line"]);
|
|
line.height = theta;
|
|
line.maxFontSize = 1.0;
|
|
|
|
var vlist = buildCommon.makeVList([
|
|
{type: "elem", elem: innerGroup},
|
|
{type: "kern", size: 3 * theta},
|
|
{type: "elem", elem: line},
|
|
{type: "kern", size: theta}
|
|
], "firstBaseline", null, options);
|
|
|
|
return makeSpan(["overline", "mord"], [vlist], options.getColor());
|
|
},
|
|
|
|
sizing: function(group, options, prev) {
|
|
var inner = buildExpression(group.value.value,
|
|
options.withSize(group.value.size), prev);
|
|
|
|
var span = makeSpan(["mord"],
|
|
[makeSpan(["sizing", "reset-" + options.size, group.value.size,
|
|
options.style.cls()],
|
|
inner)]);
|
|
|
|
var sizeToFontSize = {
|
|
"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
|
|
};
|
|
|
|
var fontSize = sizeToFontSize[group.value.size];
|
|
span.maxFontSize = fontSize * options.style.sizeMultiplier;
|
|
|
|
return span;
|
|
},
|
|
|
|
styling: function(group, options, prev) {
|
|
var style = {
|
|
"display": Style.DISPLAY,
|
|
"text": Style.TEXT,
|
|
"script": Style.SCRIPT,
|
|
"scriptscript": Style.SCRIPTSCRIPT
|
|
};
|
|
|
|
var newStyle = style[group.value.style];
|
|
|
|
var inner = buildExpression(
|
|
group.value.value, options.withStyle(newStyle), prev);
|
|
|
|
return makeSpan([options.style.reset(), newStyle.cls()], inner);
|
|
},
|
|
|
|
delimsizing: function(group, options, prev) {
|
|
var delim = group.value.value;
|
|
|
|
if (delim === ".") {
|
|
return makeSpan([groupToType[group.value.delimType]]);
|
|
}
|
|
|
|
return makeSpan(
|
|
[groupToType[group.value.delimType]],
|
|
[delimiter.sizedDelim(
|
|
delim, group.value.size, options, group.mode)]);
|
|
},
|
|
|
|
leftright: function(group, options, prev) {
|
|
var inner = buildExpression(group.value.body, options.reset());
|
|
|
|
var innerHeight = 0;
|
|
var innerDepth = 0;
|
|
|
|
for (var i = 0; i < inner.length; i++) {
|
|
innerHeight = Math.max(inner[i].height, innerHeight);
|
|
innerDepth = Math.max(inner[i].depth, innerDepth);
|
|
}
|
|
|
|
innerHeight *= options.style.sizeMultiplier;
|
|
innerDepth *= options.style.sizeMultiplier;
|
|
|
|
var leftDelim;
|
|
if (group.value.left === ".") {
|
|
leftDelim = makeSpan(["nulldelimiter"]);
|
|
} else {
|
|
leftDelim = delimiter.leftRightDelim(
|
|
group.value.left, innerHeight, innerDepth, options,
|
|
group.mode);
|
|
}
|
|
inner.unshift(leftDelim);
|
|
|
|
var rightDelim;
|
|
if (group.value.right === ".") {
|
|
rightDelim = makeSpan(["nulldelimiter"]);
|
|
} else {
|
|
rightDelim = delimiter.leftRightDelim(
|
|
group.value.right, innerHeight, innerDepth, options,
|
|
group.mode);
|
|
}
|
|
inner.push(rightDelim);
|
|
|
|
return makeSpan(["minner"], inner, options.getColor());
|
|
},
|
|
|
|
rule: function(group, options, prev) {
|
|
// Make an empty span for the rule
|
|
var rule = makeSpan(["mord", "rule"], [], options.getColor());
|
|
|
|
var width = group.value.width.number;
|
|
if (group.value.width.unit === "ex") {
|
|
width *= fontMetrics.metrics.xHeight;
|
|
}
|
|
|
|
var height = group.value.height.number;
|
|
if (group.value.height.unit === "ex") {
|
|
height *= fontMetrics.metrics.xHeight;
|
|
}
|
|
|
|
width /= options.style.sizeMultiplier;
|
|
height /= options.style.sizeMultiplier;
|
|
|
|
// Style the rule to the right size
|
|
rule.style.borderRightWidth = width + "em";
|
|
rule.style.borderTopWidth = height + "em";
|
|
|
|
// Record the height and width
|
|
rule.width = width;
|
|
rule.height = height;
|
|
|
|
return rule;
|
|
}
|
|
};
|
|
|
|
var 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
|
|
};
|
|
|
|
var buildGroup = function(group, options, prev) {
|
|
if (!group) {
|
|
return makeSpan();
|
|
}
|
|
|
|
if (groupTypes[group.type]) {
|
|
var groupNode = groupTypes[group.type](group, options, prev);
|
|
|
|
if (options.style !== options.parentStyle) {
|
|
var multiplier = options.style.sizeMultiplier /
|
|
options.parentStyle.sizeMultiplier;
|
|
|
|
groupNode.height *= multiplier;
|
|
groupNode.depth *= multiplier;
|
|
}
|
|
|
|
if (options.size !== options.parentSize) {
|
|
var multiplier = sizingMultiplier[options.size] /
|
|
sizingMultiplier[options.parentSize];
|
|
|
|
groupNode.height *= multiplier;
|
|
groupNode.depth *= multiplier;
|
|
}
|
|
|
|
return groupNode;
|
|
} else {
|
|
throw new ParseError(
|
|
"Got group of unknown type: '" + group.type + "'");
|
|
}
|
|
};
|
|
|
|
var buildTree = function(tree) {
|
|
// Setup the default options
|
|
var options = new Options(Style.TEXT, "size5", "");
|
|
|
|
var expression = buildExpression(tree, options);
|
|
var span = makeSpan(["base", options.style.cls()], expression);
|
|
var topStrut = makeSpan(["strut"]);
|
|
var bottomStrut = makeSpan(["strut", "bottom"]);
|
|
|
|
topStrut.style.height = span.height + "em";
|
|
bottomStrut.style.height = (span.height + span.depth) + "em";
|
|
// We'd like to use `vertical-align: top` but in IE 9 this lowers the
|
|
// baseline of the box to the bottom of this strut (instead staying in the
|
|
// normal place) so we use an absolute value for vertical-align instead
|
|
bottomStrut.style.verticalAlign = -span.depth + "em";
|
|
|
|
var katexNode = makeSpan(["katex"], [
|
|
makeSpan(["katex-inner"], [topStrut, bottomStrut, span])
|
|
]);
|
|
|
|
return katexNode;
|
|
};
|
|
|
|
module.exports = buildTree;
|