
Summary:
Also, rename all of our uses of fonts to use the uppercased versions. We want to
use the uppercase versions because it makes updating and modifying the fonts
much easier (since the font names inside the actual font files are uppercased).
Test Plan:
- Make sure the huxley screenshots look good (You can compare a diff of them
on github at
f90d093361
By my eye, it seems like some things have moved up ~1/2 pixel, and some of
the fonts have maybe slightly changed shape, like the large `b` in
SizingBaseline)
Reviewers: alpert
Reviewed By: alpert
Differential Revision: http://phabricator.khanacademy.org/D11979
728 lines
23 KiB
JavaScript
728 lines
23 KiB
JavaScript
var Options = require("./Options");
|
|
var ParseError = require("./ParseError");
|
|
var Style = require("./Style");
|
|
|
|
var domTree = require("./domTree");
|
|
var fontMetrics = require("./fontMetrics");
|
|
var parseTree = require("./parseTree");
|
|
var utils = require("./utils");
|
|
var symbols = require("./symbols");
|
|
|
|
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 makeSpan = function(classes, children, color) {
|
|
var height = 0;
|
|
var depth = 0;
|
|
|
|
if (children) {
|
|
for (var i = 0; i < children.length; i++) {
|
|
if (children[i].height > height) {
|
|
height = children[i].height;
|
|
}
|
|
if (children[i].depth > depth) {
|
|
depth = children[i].depth;
|
|
}
|
|
}
|
|
}
|
|
|
|
var span = new domTree.span(classes, children, height, depth);
|
|
|
|
if (color) {
|
|
span.style.color = color;
|
|
}
|
|
|
|
return span;
|
|
};
|
|
|
|
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"
|
|
};
|
|
|
|
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 === "delimsizing") {
|
|
return group.value.type;
|
|
} 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"],
|
|
[mathit(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
textord: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mord"],
|
|
[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(["bin", "open", "rel", "op", "punct"],
|
|
prevAtom.type)) {
|
|
group.type = "ord";
|
|
className = "mord";
|
|
}
|
|
return makeSpan(
|
|
[className],
|
|
[mathrm(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
rel: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mrel"],
|
|
[mathrm(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
text: function(group, options, prev) {
|
|
return makeSpan(["text mord", options.style.cls()],
|
|
[buildGroup(group.value, options.deepen())]
|
|
);
|
|
},
|
|
|
|
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()).deepen());
|
|
var supmid = makeSpan(
|
|
[options.style.reset(), options.style.sup().cls()], [sup]);
|
|
var supwrap = makeSpan(["msup", options.style.reset()], [supmid]);
|
|
}
|
|
|
|
if (group.value.sub) {
|
|
var sub = buildGroup(group.value.sub,
|
|
options.withStyle(options.style.sub()).deepen());
|
|
var submid = makeSpan(
|
|
[options.style.reset(), options.style.sub().cls()], [sub]);
|
|
var subwrap = makeSpan(["msub"], [submid]);
|
|
}
|
|
|
|
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 supsub;
|
|
var fixIE = makeSpan(["fix-ie"], [new domTree.textNode("\u00a0")]);
|
|
|
|
if (!group.value.sup) {
|
|
v = Math.max(v, fontMetrics.metrics.sub1,
|
|
sub.height - 0.8 * fontMetrics.metrics.xHeight);
|
|
|
|
subwrap.style.top = v + "em";
|
|
|
|
subwrap.depth = subwrap.depth + v;
|
|
subwrap.height = 0;
|
|
|
|
supsub = makeSpan(["msupsub"], [subwrap, fixIE]);
|
|
} else if (!group.value.sub) {
|
|
u = Math.max(u, p,
|
|
sup.depth + 0.25 * fontMetrics.metrics.xHeight);
|
|
|
|
supwrap.style.top = -u + "em";
|
|
|
|
supwrap.height = supwrap.height + u;
|
|
supwrap.depth = 0;
|
|
|
|
supsub = makeSpan(["msupsub"], [supwrap, fixIE]);
|
|
} 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;
|
|
}
|
|
}
|
|
|
|
supwrap.style.top = -u + "em";
|
|
subwrap.style.top = v + "em";
|
|
|
|
supwrap.height = supwrap.height + u;
|
|
supwrap.depth = 0;
|
|
|
|
subwrap.height = 0;
|
|
subwrap.depth = subwrap.depth + v;
|
|
|
|
supsub = makeSpan(["msupsub"], [supwrap, subwrap, fixIE]);
|
|
}
|
|
|
|
return makeSpan([getTypeOfGroup(group.value.base)], [base, supsub]);
|
|
},
|
|
|
|
open: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mopen"],
|
|
[mathrm(group.value, group.mode)],
|
|
options.getColor()
|
|
);
|
|
},
|
|
|
|
close: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mclose"],
|
|
[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).deepen());
|
|
var numernumer = makeSpan([fstyle.reset(), nstyle.cls()], [numer]);
|
|
var numerrow = makeSpan(["mfracnum"], [numernumer]);
|
|
|
|
var mid = makeSpan(["mfracmid"], [makeSpan()]);
|
|
|
|
var denom = buildGroup(group.value.denom, options.withStyle(dstyle).deepen());
|
|
var denomdenom = makeSpan([fstyle.reset(), dstyle.cls()], [denom])
|
|
var denomrow = makeSpan(["mfracden"], [denomdenom]);
|
|
|
|
var theta = fontMetrics.metrics.defaultRuleThickness;
|
|
|
|
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"], [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 els = buildExpression(
|
|
group.value.value,
|
|
options.withColor(group.value.color),
|
|
prev
|
|
);
|
|
|
|
var height = 0;
|
|
var depth = 0;
|
|
|
|
for (var i = 0; i < els.length; i++) {
|
|
if (els[i].height > height) {
|
|
var height = els[i].height;
|
|
}
|
|
if (els[i].depth > depth) {
|
|
var depth = els[i].depth;
|
|
}
|
|
}
|
|
|
|
return new domTree.documentFragment(els, height, depth);
|
|
},
|
|
|
|
spacing: function(group, options, prev) {
|
|
if (group.value === "\\ " || group.value === "\\space" ||
|
|
group.value === " " || group.value === "~") {
|
|
return makeSpan(
|
|
["mord", "mspace"],
|
|
[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, options.deepen())]);
|
|
var fix = makeSpan(["fix"], []);
|
|
return makeSpan(["llap", options.style.cls()], [inner, fix]);
|
|
},
|
|
|
|
rlap: function(group, options, prev) {
|
|
var inner = makeSpan(
|
|
["inner"], [buildGroup(group.value, options.deepen())]);
|
|
var fix = makeSpan(["fix"], []);
|
|
return makeSpan(["rlap", options.style.cls()], [inner, fix]);
|
|
},
|
|
|
|
punct: function(group, options, prev) {
|
|
return makeSpan(
|
|
["mpunct"],
|
|
[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.length; i++) {
|
|
chars.push(mathrm(group.value[i], group.mode));
|
|
}
|
|
|
|
return makeSpan(["mop"], chars, options.getColor());
|
|
},
|
|
|
|
katex: function(group, options, prev) {
|
|
var k = makeSpan(["k"], [mathrm("K", group.mode)]);
|
|
var a = makeSpan(["a"], [mathrm("A", group.mode)]);
|
|
|
|
a.height = (a.height + 0.2) * 0.75;
|
|
a.depth = (a.height - 0.2) * 0.75;
|
|
|
|
var t = makeSpan(["t"], [mathrm("T", group.mode)]);
|
|
var e = makeSpan(["e"], [mathrm("E", group.mode)]);
|
|
|
|
e.height = (e.height - 0.2155);
|
|
e.depth = (e.depth + 0.2155);
|
|
|
|
var x = makeSpan(["x"], [mathrm("X", group.mode)]);
|
|
|
|
return makeSpan(["katex-logo"], [k, a, t, e, x], options.getColor());
|
|
},
|
|
|
|
overline: function(group, options, prev) {
|
|
var innerGroup = buildGroup(group.value.result,
|
|
options.withStyle(options.style.cramp()).deepen());
|
|
|
|
// The theta variable in the TeXbook
|
|
var lineWidth = fontMetrics.metrics.defaultRuleThickness;
|
|
|
|
var line = makeSpan(["overline-line"], [makeSpan([])]);
|
|
var inner = makeSpan(["overline-inner"], [innerGroup]);
|
|
var fixIE = makeSpan(["fix-ie"], []);
|
|
|
|
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 = buildGroup(group.value.value,
|
|
options.withSize(group.value.size), prev);
|
|
|
|
return makeSpan(
|
|
["sizing", "reset-" + options.size, group.value.size,
|
|
getTypeOfGroup(group.value.value)],
|
|
[inner]);
|
|
},
|
|
|
|
delimsizing: function(group, options, prev) {
|
|
var normalDelimiters = [
|
|
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
|
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
|
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
|
|
"<", ">", "\\langle", "\\rangle", "/", "\\backslash"
|
|
];
|
|
|
|
var stackDelimiters = [
|
|
"\\uparrow", "\\downarrow", "\\updownarrow",
|
|
"\\Uparrow", "\\Downarrow", "\\Updownarrow",
|
|
"|", "\\|", "\\vert", "\\Vert"
|
|
];
|
|
|
|
// Metrics of the different sizes. Found by looking at TeX's output of
|
|
// $\bigl| \Bigl| \biggl| \Biggl| \showlists$
|
|
var sizeToMetrics = {
|
|
1: {height: .85, depth: .35},
|
|
2: {height: 1.15, depth: .65},
|
|
3: {height: 1.45, depth: .95},
|
|
4: {height: 1.75, depth: 1.25}
|
|
};
|
|
|
|
// Make an inner span with the given offset and in the given font
|
|
var makeInner = function(symbol, offset, font) {
|
|
var sizeClass;
|
|
if (font === "Size1-Regular") {
|
|
sizeClass = "size1";
|
|
}
|
|
|
|
var inner = makeSpan(
|
|
["delimsizinginner", sizeClass],
|
|
[makeSpan([], [makeText(symbol, font, group.mode)])]);
|
|
|
|
inner.style.top = offset + "em";
|
|
inner.height -= offset;
|
|
inner.depth += offset;
|
|
|
|
return inner;
|
|
};
|
|
|
|
// Get the metrics for a given symbol and font, after transformation
|
|
var getMetrics = function(symbol, font) {
|
|
if (symbols["math"][symbol] && symbols["math"][symbol].replace) {
|
|
return fontMetrics.getCharacterMetrics(
|
|
symbols["math"][symbol].replace, font);
|
|
} else {
|
|
return fontMetrics.getCharacterMetrics(
|
|
symbol, font);
|
|
}
|
|
};
|
|
|
|
var original = group.value.value;
|
|
|
|
if (utils.contains(normalDelimiters, original)) {
|
|
// These delimiters can be created by simply using the size1-size4
|
|
// fonts, so they don't require special treatment
|
|
if (original === "<") {
|
|
original = "\\langle";
|
|
} else if (original === ">") {
|
|
original = "\\rangle";
|
|
}
|
|
|
|
var size = "size" + group.value.size;
|
|
var inner = mathrmSize(
|
|
original, group.value.size, group.mode);
|
|
|
|
return makeSpan(
|
|
["delimsizing", size, groupToType[group.value.type]],
|
|
[inner], options.getColor());
|
|
} else if (utils.contains(stackDelimiters, original)) {
|
|
// These delimiters can be created by stacking other delimiters on
|
|
// top of each other to create the correct size
|
|
|
|
// There are three parts, the top, a repeated middle, and a bottom.
|
|
var top = middle = bottom = original;
|
|
var font = "Size1-Regular";
|
|
var overlap = false;
|
|
|
|
// We set the parts and font based on the symbol. Note that we use
|
|
// '\u23d0' instead of '|' and '\u2016' instead of '\\|' for the
|
|
// middles of the arrows
|
|
if (original === "\\uparrow") {
|
|
middle = bottom = "\u23d0";
|
|
} else if (original === "\\Uparrow") {
|
|
middle = bottom = "\u2016";
|
|
} else if (original === "\\downarrow") {
|
|
top = middle = "\u23d0";
|
|
} else if (original === "\\Downarrow") {
|
|
top = middle = "\u2016";
|
|
} else if (original === "\\updownarrow") {
|
|
top = "\\uparrow";
|
|
middle = "\u23d0";
|
|
bottom = "\\downarrow";
|
|
} else if (original === "\\Updownarrow") {
|
|
top = "\\Uparrow";
|
|
middle = "\u2016";
|
|
bottom = "\\Downarrow";
|
|
} else if (original === "|" || original === "\\vert") {
|
|
overlap = true;
|
|
} else if (original === "\\|" || original === "\\Vert") {
|
|
overlap = true;
|
|
}
|
|
|
|
// Get the metrics of the final symbol
|
|
var metrics = sizeToMetrics[group.value.size];
|
|
var heightTotal = metrics.height + metrics.depth;
|
|
|
|
// Get the metrics of the three sections
|
|
var topMetrics = getMetrics(top, font);
|
|
var topHeightTotal = topMetrics.height + topMetrics.depth;
|
|
var middleMetrics = getMetrics(middle, font);
|
|
var middleHeightTotal = middleMetrics.height + middleMetrics.depth;
|
|
var bottomMetrics = getMetrics(bottom, font);
|
|
var bottomHeightTotal = bottomMetrics.height + bottomMetrics.depth;
|
|
|
|
var middleHeight = heightTotal - topHeightTotal - bottomHeightTotal;
|
|
var symbolCount = Math.ceil(middleHeight / middleHeightTotal);
|
|
|
|
if (overlap) {
|
|
// 2 * overlapAmount + middleHeight =
|
|
// (symbolCount - 1) * (middleHeightTotal - overlapAmount) +
|
|
// middleHeightTotal
|
|
var overlapAmount = (symbolCount * middleHeightTotal -
|
|
middleHeight) / (symbolCount + 1);
|
|
} else {
|
|
var overlapAmount = 0;
|
|
}
|
|
|
|
// Keep a list of the inner spans
|
|
var inners = [];
|
|
|
|
// Add the top symbol
|
|
inners.push(
|
|
makeInner(top, topMetrics.height - metrics.height, font));
|
|
|
|
// Add middle symbols until there's only space for the bottom symbol
|
|
var curr_height = metrics.height - topHeightTotal + overlapAmount;
|
|
for (var i = 0; i < symbolCount; i++) {
|
|
inners.push(
|
|
makeInner(middle, middleMetrics.height - curr_height, font));
|
|
curr_height -= middleHeightTotal - overlapAmount;
|
|
}
|
|
|
|
// Add the bottom symbol
|
|
inners.push(
|
|
makeInner(bottom, metrics.depth - bottomMetrics.depth, font));
|
|
|
|
var fixIE = makeSpan(["fix-ie"], [new domTree.textNode("\u00a0")]);
|
|
inners.push(fixIE);
|
|
|
|
return makeSpan(
|
|
["delimsizing", "mult", groupToType[group.value.type]],
|
|
inners, options.getColor());
|
|
} else {
|
|
throw new ParseError("Illegal delimiter: '" + original + "'");
|
|
}
|
|
}
|
|
};
|
|
|
|
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];
|
|
|
|
if (options.deep) {
|
|
throw new ParseError(
|
|
"Can't use sizing outside of the root node");
|
|
}
|
|
|
|
groupNode.height *= multiplier;
|
|
groupNode.depth *= multiplier;
|
|
}
|
|
|
|
return groupNode;
|
|
} else {
|
|
throw new ParseError(
|
|
"Got group of unknown type: '" + group.type + "'");
|
|
}
|
|
};
|
|
|
|
var makeText = function(value, style, mode) {
|
|
if (symbols[mode][value] && symbols[mode][value].replace) {
|
|
value = symbols[mode][value].replace;
|
|
}
|
|
|
|
var metrics = fontMetrics.getCharacterMetrics(value, style);
|
|
|
|
if (metrics) {
|
|
var textNode = new domTree.textNode(value, metrics.height,
|
|
metrics.depth);
|
|
if (metrics.italic > 0) {
|
|
var span = makeSpan([], [textNode]);
|
|
span.style.marginRight = metrics.italic + "em";
|
|
|
|
return span;
|
|
} else {
|
|
return textNode;
|
|
}
|
|
} else {
|
|
console && console.warn("No character metrics for '" + value +
|
|
"' in style '" + style + "'");
|
|
return new domTree.textNode(value, 0, 0);
|
|
}
|
|
};
|
|
|
|
var mathit = function(value, mode) {
|
|
return makeSpan(["mathit"], [makeText(value, "Math-Italic", mode)]);
|
|
};
|
|
|
|
var mathrm = function(value, mode) {
|
|
if (symbols[mode][value].font === "main") {
|
|
return makeText(value, "Main-Regular", mode);
|
|
} else {
|
|
return makeSpan(["amsrm"], [makeText(value, "AMS-Regular", mode)]);
|
|
}
|
|
};
|
|
|
|
var mathrmSize = function(value, size, mode) {
|
|
return makeText(value, "Size" + size + "-Regular", mode);
|
|
}
|
|
|
|
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.toDOM();
|
|
};
|
|
|
|
module.exports = buildTree;
|