
Summary: Add support for math-mode accents. This involves a couple changes. First, in order to correctly position the accents, we must know the kern between every character and the "skewchar" in that font. To do this, we improve our tfm parser to run the mini-kern-language and calculate kerns. We then export these into fontMetrics.js. Then, we add normal support for accents. In particular, we do some special handling for supsubs around accents. This involves building the supsub separately without the accent, and then replacing its base with the built accent. Finally, the character in the fonts for the \vec command is a combining unicode character, so it is shifted to the left, but none of the other characters do this. We add some special handling for \vec to account for this. Fixes #7 Test Plan: - Make sure tests pass - Make sure no huxley screenshots changed, and the new one looks good Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D13157
235 lines
8.2 KiB
JavaScript
235 lines
8.2 KiB
JavaScript
var domTree = require("./domTree");
|
|
var fontMetrics = require("./fontMetrics");
|
|
var symbols = require("./symbols");
|
|
|
|
var makeSymbol = function(value, style, mode, color, classes) {
|
|
if (symbols[mode][value] && symbols[mode][value].replace) {
|
|
value = symbols[mode][value].replace;
|
|
}
|
|
|
|
var metrics = fontMetrics.getCharacterMetrics(value, style);
|
|
|
|
var symbolNode;
|
|
if (metrics) {
|
|
symbolNode = new domTree.symbolNode(
|
|
value, metrics.height, metrics.depth, metrics.italic, metrics.skew,
|
|
classes);
|
|
} else {
|
|
console && console.warn("No character metrics for '" + value +
|
|
"' in style '" + style + "'");
|
|
symbolNode = new domTree.symbolNode(value, 0, 0, 0, 0, classes);
|
|
}
|
|
|
|
if (color) {
|
|
symbolNode.style.color = color;
|
|
}
|
|
|
|
return symbolNode;
|
|
};
|
|
|
|
var mathit = function(value, mode, color, classes) {
|
|
return makeSymbol(
|
|
value, "Math-Italic", mode, color, classes.concat(["mathit"]));
|
|
};
|
|
|
|
var mathrm = function(value, mode, color, classes) {
|
|
if (symbols[mode][value].font === "main") {
|
|
return makeSymbol(value, "Main-Regular", mode, color, classes);
|
|
} else {
|
|
return makeSymbol(
|
|
value, "AMS-Regular", mode, color, classes.concat(["amsrm"]));
|
|
}
|
|
};
|
|
|
|
var sizeElementFromChildren = function(elem) {
|
|
var height = 0;
|
|
var depth = 0;
|
|
var maxFontSize = 0;
|
|
|
|
if (elem.children) {
|
|
for (var i = 0; i < elem.children.length; i++) {
|
|
if (elem.children[i].height > height) {
|
|
height = elem.children[i].height;
|
|
}
|
|
if (elem.children[i].depth > depth) {
|
|
depth = elem.children[i].depth;
|
|
}
|
|
if (elem.children[i].maxFontSize > maxFontSize) {
|
|
maxFontSize = elem.children[i].maxFontSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
elem.height = height;
|
|
elem.depth = depth;
|
|
elem.maxFontSize = maxFontSize;
|
|
};
|
|
|
|
var makeSpan = function(classes, children, color) {
|
|
var span = new domTree.span(classes, children);
|
|
|
|
sizeElementFromChildren(span);
|
|
|
|
if (color) {
|
|
span.style.color = color;
|
|
}
|
|
|
|
return span;
|
|
};
|
|
|
|
var makeFragment = function(children) {
|
|
var fragment = new domTree.documentFragment(children);
|
|
|
|
sizeElementFromChildren(fragment);
|
|
|
|
return fragment;
|
|
};
|
|
|
|
var makeFontSizer = function(options, fontSize) {
|
|
var fontSizeInner = makeSpan([], [new domTree.symbolNode("\u200b")]);
|
|
fontSizeInner.style.fontSize = (fontSize / options.style.sizeMultiplier) + "em";
|
|
|
|
var fontSizer = makeSpan(
|
|
["fontsize-ensurer", "reset-" + options.size, "size5"],
|
|
[fontSizeInner]);
|
|
|
|
return fontSizer;
|
|
};
|
|
|
|
/*
|
|
* Makes a vertical list by stacking elements and kerns on top of each other.
|
|
* Allows for many different ways of specifying the positioning method.
|
|
*
|
|
* Arguments:
|
|
* - children: A list of child or kern nodes to be stacked on top of each other
|
|
* (i.e. the first element will be at the bottom, and the last at
|
|
* the top). Element nodes are specified as
|
|
* {type: "elem", elem: node}
|
|
* while kern nodes are specified as
|
|
* {type: "kern", size: size}
|
|
* - positionType: The method by which the vlist should be positioned. Valid
|
|
* values are:
|
|
* - "individualShift": The children list only contains elem
|
|
* nodes, and each node contains an extra
|
|
* "shift" value of how much it should be
|
|
* shifted (note that shifting is always
|
|
* moving downwards). positionData is
|
|
* ignored.
|
|
* - "top": The positionData specifies the topmost point of
|
|
* the vlist (note this is expected to be a height,
|
|
* so positive values move up)
|
|
* - "bottom": The positionData specifies the bottommost point
|
|
* of the vlist (note this is expected to be a
|
|
* depth, so positive values move down
|
|
* - "shift": The vlist will be positioned such that its
|
|
* baseline is positionData away from the baseline
|
|
* of the first child. Positive values move
|
|
* downwards.
|
|
* - "firstBaseline": The vlist will be positioned such that
|
|
* its baseline is aligned with the
|
|
* baseline of the first child.
|
|
* positionData is ignored. (this is
|
|
* equivalent to "shift" with
|
|
* positionData=0)
|
|
* - positionData: Data used in different ways depending on positionType
|
|
* - options: An Options object
|
|
*
|
|
*/
|
|
var makeVList = function(children, positionType, positionData, options) {
|
|
var depth;
|
|
if (positionType === "individualShift") {
|
|
var oldChildren = children;
|
|
children = [oldChildren[0]];
|
|
|
|
// Add in kerns to the list of children to get each element to be
|
|
// shifted to the correct specified shift
|
|
depth = -oldChildren[0].shift - oldChildren[0].elem.depth;
|
|
var currPos = depth;
|
|
for (var i = 1; i < oldChildren.length; i++) {
|
|
var diff = -oldChildren[i].shift - currPos -
|
|
oldChildren[i].elem.depth;
|
|
var size = diff -
|
|
(oldChildren[i - 1].elem.height +
|
|
oldChildren[i - 1].elem.depth);
|
|
|
|
currPos = currPos + diff;
|
|
|
|
children.push({type: "kern", size: size});
|
|
children.push(oldChildren[i]);
|
|
}
|
|
} else if (positionType === "top") {
|
|
// We always start at the bottom, so calculate the bottom by adding up
|
|
// all the sizes
|
|
var bottom = positionData;
|
|
for (var i = 0; i < children.length; i++) {
|
|
if (children[i].type === "kern") {
|
|
bottom -= children[i].size;
|
|
} else {
|
|
bottom -= children[i].elem.height + children[i].elem.depth;
|
|
}
|
|
}
|
|
depth = bottom;
|
|
} else if (positionType === "bottom") {
|
|
depth = -positionData;
|
|
} else if (positionType === "shift") {
|
|
depth = -children[0].elem.depth - positionData;
|
|
} else if (positionType === "firstBaseline") {
|
|
depth = -children[0].elem.depth;
|
|
} else {
|
|
depth = 0;
|
|
}
|
|
|
|
// Make the fontSizer
|
|
var maxFontSize = 0;
|
|
for (var i = 0; i < children.length; i++) {
|
|
if (children[i].type === "elem") {
|
|
maxFontSize = Math.max(maxFontSize, children[i].elem.maxFontSize);
|
|
}
|
|
}
|
|
var fontSizer = makeFontSizer(options, maxFontSize);
|
|
|
|
// Create a new list of actual children at the correct offsets
|
|
var realChildren = [];
|
|
var currPos = depth;
|
|
for (var i = 0; i < children.length; i++) {
|
|
if (children[i].type === "kern") {
|
|
currPos += children[i].size;
|
|
} else {
|
|
var child = children[i].elem;
|
|
|
|
var shift = -child.depth - currPos;
|
|
currPos += child.height + child.depth;
|
|
|
|
var childWrap = makeSpan([], [fontSizer, child]);
|
|
childWrap.height -= shift;
|
|
childWrap.depth += shift;
|
|
childWrap.style.top = shift + "em";
|
|
|
|
realChildren.push(childWrap);
|
|
}
|
|
}
|
|
|
|
// Add in an element at the end with no offset to fix the calculation of
|
|
// baselines in some browsers (namely IE, sometimes safari)
|
|
var baselineFix = makeSpan(
|
|
["baseline-fix"], [fontSizer, new domTree.symbolNode("\u200b")]);
|
|
realChildren.push(baselineFix);
|
|
|
|
var vlist = makeSpan(["vlist"], realChildren);
|
|
// Fix the final height and depth, in case there were kerns at the ends
|
|
// since the makeSpan calculation won't take that in to account.
|
|
vlist.height = Math.max(currPos, vlist.height);
|
|
vlist.depth = Math.max(-depth, vlist.depth);
|
|
return vlist;
|
|
};
|
|
|
|
module.exports = {
|
|
makeSymbol: makeSymbol,
|
|
mathit: mathit,
|
|
mathrm: mathrm,
|
|
makeSpan: makeSpan,
|
|
makeFragment: makeFragment,
|
|
makeFontSizer: makeFontSizer,
|
|
makeVList: makeVList
|
|
};
|