
Summary: Move dom creation into katex.js so our tests can test non-dom things, and add some buildTree tests. Add some checks make utils.js work in node. Add support for jasmine-node, to allow for command line unit testing. Test Plan: - Make sure tests work, in both the browser and with `make test` - Make sure huxley screenshots didn't change Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D13125
715 lines
22 KiB
JavaScript
715 lines
22 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",
|
|
namedfn: "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 groupTypes = {
|
|
mathord: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mord"],
|
|
[buildCommon.mathit(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
textord: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mord"],
|
|
[buildCommon.mathrm(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
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 makeSpan(
|
|
[className],
|
|
[buildCommon.mathrm(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
rel: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mrel"],
|
|
[buildCommon.mathrm(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
text: function(group, options, prev) {
|
|
return makeSpan(["text mord", options.style.cls()],
|
|
buildExpression(group.value.body, options.reset()));
|
|
},
|
|
|
|
supsub: function(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]);
|
|
}
|
|
|
|
if (isCharacterBox(group.value.base)) {
|
|
var u = 0;
|
|
var v = 0;
|
|
} else {
|
|
var u = base.height - fontMetrics.metrics.supDrop;
|
|
var 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) {
|
|
var fontSizer = buildCommon.makeFontSizer(options, submid.maxFontSize);
|
|
var subwrap = makeSpan(["msub"], [fontSizer, submid]);
|
|
|
|
v = Math.max(v, fontMetrics.metrics.sub1,
|
|
sub.height - 0.8 * fontMetrics.metrics.xHeight);
|
|
|
|
subwrap.style.top = v + "em";
|
|
subwrap.style.marginRight = scriptspace;
|
|
|
|
subwrap.depth = subwrap.depth + v;
|
|
subwrap.height = 0;
|
|
|
|
var fixIE = makeSpan(["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
|
|
|
|
supsub = makeSpan(["msupsub"], [subwrap, fixIE]);
|
|
} else if (!group.value.sub) {
|
|
var fontSizer = buildCommon.makeFontSizer(options, supmid.maxFontSize);
|
|
var supwrap = makeSpan(["msup"], [fontSizer, supmid]);
|
|
|
|
u = Math.max(u, p,
|
|
sup.depth + 0.25 * fontMetrics.metrics.xHeight);
|
|
|
|
supwrap.style.top = -u + "em";
|
|
supwrap.style.marginRight = scriptspace;
|
|
|
|
supwrap.height = supwrap.height + u;
|
|
supwrap.depth = 0;
|
|
|
|
var fixIE = makeSpan(["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
|
|
|
|
supsub = makeSpan(["msupsub"], [supwrap, fixIE]);
|
|
} else {
|
|
var fontSizer = buildCommon.makeFontSizer(options,
|
|
Math.max(submid.maxFontSize, supmid.maxFontSize));
|
|
var subwrap = makeSpan(["msub"], [fontSizer, submid]);
|
|
var supwrap = makeSpan(["msup"], [fontSizer, supmid]);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
supwrap.style.top = -u + "em";
|
|
subwrap.style.top = v + "em";
|
|
|
|
supwrap.style.marginRight = scriptspace;
|
|
subwrap.style.marginRight = scriptspace;
|
|
|
|
supwrap.height = supwrap.height + u;
|
|
supwrap.depth = 0;
|
|
|
|
subwrap.height = 0;
|
|
subwrap.depth = subwrap.depth + v;
|
|
|
|
var fixIE = makeSpan(["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
|
|
|
|
supsub = makeSpan(["msupsub"], [supwrap, subwrap, fixIE]);
|
|
}
|
|
|
|
return makeSpan([getTypeOfGroup(group.value.base)],
|
|
[base, supsub]);
|
|
},
|
|
|
|
open: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mopen"],
|
|
[buildCommon.mathrm(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
close: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mclose"],
|
|
[buildCommon.mathrm(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
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 numernumer = makeSpan([fstyle.reset(), nstyle.cls()], [numer]);
|
|
|
|
var denom = buildGroup(group.value.denom, options.withStyle(dstyle));
|
|
var denomdenom = makeSpan([fstyle.reset(), dstyle.cls()], [denom])
|
|
|
|
var fontSizer = buildCommon.makeFontSizer(options,
|
|
Math.max(numer.maxFontSize, denom.maxFontSize));
|
|
|
|
var line = makeSpan([options.style.reset(), Style.TEXT.cls(), "line"]);
|
|
|
|
var numerrow = makeSpan(["mfracnum"], [fontSizer, numernumer]);
|
|
var mid = makeSpan(["mfracmid"], [fontSizer, line]);
|
|
var denomrow = makeSpan(["mfracden"], [fontSizer, denomdenom]);
|
|
|
|
var theta = fontMetrics.metrics.defaultRuleThickness / options.style.sizeMultiplier;
|
|
|
|
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));
|
|
}
|
|
|
|
numerrow.style.top = -u + "em";
|
|
mid.style.top = -(a - 0.5 * theta) + "em";
|
|
denomrow.style.top = v + "em";
|
|
|
|
numerrow.height = numerrow.height + u;
|
|
numerrow.depth = 0;
|
|
|
|
denomrow.height = 0;
|
|
denomrow.depth = denomrow.depth + v;
|
|
|
|
var fixIE = makeSpan(["fix-ie"], [
|
|
fontSizer, new domTree.textNode("\u00a0")]);
|
|
|
|
var frac = makeSpan([], [numerrow, mid, denomrow, fixIE]);
|
|
|
|
frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
|
frac.depth *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
|
|
|
var wrap = makeSpan(
|
|
[options.style.reset(), fstyle.cls()], [frac]);
|
|
|
|
return makeSpan(["minner"], [
|
|
makeSpan(["mfrac"], [wrap])
|
|
], 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 makeSpan(
|
|
["mpunct"],
|
|
[buildCommon.mathrm(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
ordgroup: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mord", options.style.cls()],
|
|
buildExpression(group.value, options.reset())
|
|
);
|
|
},
|
|
|
|
namedfn: function(group, options, prev) {
|
|
var chars = [];
|
|
for (var i = 1; i < group.value.body.length; i++) {
|
|
chars.push(buildCommon.mathrm(group.value.body[i], group.mode));
|
|
}
|
|
|
|
return makeSpan(["mop"], chars, options.getColor());
|
|
},
|
|
|
|
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 innerGroup = buildGroup(group.value.body,
|
|
options.withStyle(options.style.cramp()));
|
|
|
|
var fontSizer = buildCommon.makeFontSizer(
|
|
options, Math.max(innerGroup.maxFontSize, 1.0));
|
|
|
|
// The theta variable in the TeXbook
|
|
var lineWidth = fontMetrics.metrics.defaultRuleThickness;
|
|
|
|
var lineInner =
|
|
makeSpan([options.style.reset(), Style.TEXT.cls(), "line"]);
|
|
lineInner.maxFontSize = 1.0;
|
|
var line = makeSpan(["sqrt-line"], [fontSizer, lineInner]);
|
|
|
|
var inner = makeSpan(["sqrt-inner"], [fontSizer, innerGroup]);
|
|
var fixIE = makeSpan(
|
|
["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
|
|
|
|
var theta = fontMetrics.metrics.defaultRuleThickness /
|
|
options.style.sizeMultiplier;
|
|
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)]);
|
|
|
|
var delimDepth = delim.height + delim.depth;
|
|
|
|
if (delimDepth > inner.height + inner.depth + psi) {
|
|
psi = (psi + delimDepth - inner.height - inner.depth) / 2;
|
|
}
|
|
|
|
delim.style.top = (-inner.height - psi + delim.height - theta) + "em";
|
|
|
|
line.style.top = (-inner.height - psi) + "em";
|
|
line.height = inner.height + psi + 2 * theta;
|
|
|
|
// 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 = makeSpan(["sqrt-body"], [line, inner, fixIE]);
|
|
}
|
|
|
|
return makeSpan(["sqrt", "mord"], [delim, body]);
|
|
},
|
|
|
|
overline: function(group, options, prev) {
|
|
var innerGroup = buildGroup(group.value.body,
|
|
options.withStyle(options.style.cramp()));
|
|
|
|
var fontSizer = buildCommon.makeFontSizer(options, innerGroup.maxFontSize);
|
|
|
|
// The theta variable in the TeXbook
|
|
var lineWidth = fontMetrics.metrics.defaultRuleThickness /
|
|
options.style.sizeMultiplier;
|
|
|
|
var line = makeSpan(
|
|
["overline-line"], [fontSizer, makeSpan([options.style.reset(), Style.TEXT.cls(), "line"])]);
|
|
var inner = makeSpan(["overline-inner"], [fontSizer, innerGroup]);
|
|
var fixIE = makeSpan(
|
|
["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
|
|
|
|
line.style.top = (-inner.height - 3 * lineWidth) + "em";
|
|
// The line is supposed to have 1 extra line width above it in height
|
|
// (TeXbook pg. 443, nr. 9)
|
|
line.height = inner.height + 5 * lineWidth;
|
|
|
|
return makeSpan(["overline", "mord"], [
|
|
line, inner, fixIE
|
|
], 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;
|