mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-09 13:08:40 +00:00
Summary: Add a way to automatically build vlists correctly. Previously, we built vlists manually in ~4 different places, which made it difficult to manage changes, and led to a large amount of duplication in the less. This also fixes the vlist construction in safari, where the `display: inline-table` wasn't being applied because of CSS specificity. This leads to the only significant change in the huxley tests, with the vertical spacing. Test Plan: - Make sure the tests still work - Make sure most of the huxley screenshots didn't change, and that the new changes are insignificant. - Make sure vlists now work in Safari - Make sure the change to the VerticalSpacing screenshot is caused by the fix-baseline span now correctly applying `display: inline-table` by creating the construct in master and adding `display: inline-table !important` to the `.fix-ie` css rule Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D13082
233 lines
8.2 KiB
JavaScript
233 lines
8.2 KiB
JavaScript
var domTree = require("./domTree");
|
|
var fontMetrics = require("./fontMetrics");
|
|
var symbols = require("./symbols");
|
|
|
|
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 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.textNode("\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.textNode("\u00a0")]);
|
|
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 = {
|
|
makeText: makeText,
|
|
mathit: mathit,
|
|
mathrm: mathrm,
|
|
makeSpan: makeSpan,
|
|
makeFragment: makeFragment,
|
|
makeFontSizer: makeFontSizer,
|
|
makeVList: makeVList
|
|
};
|