Add support for \left and \right
Summary: Added stacked delimiter support for more delimiters. Split out delimiter functions into its own file, and split out some tree building functions into a common file. Supports the empty `.` delimiter with \left and \right, and doesn't try to produce huge /, \backslash, <, or > delimiters. Depends on D7844 Test input: \left( \left) \left[ \left\lbrack \left] \left\rbrack \left\{ \left\lbrace \left\} \left\rbrace \left\lfloor \left\rfloor \left\lceil \left\rceil \left\langle \left\rangle \left/ \left\backslash \left| \left\vert \left\| \left\Vert \left\uparrow \left\Uparrow \left\downarrow \left\Downarrow \left\updownarrow \left\Updownarrow {x^{x^{x^{x^{x^{x^{x^{x^{x^{x^x}}}}}}}}}} \right.\right.\right.\right.\right.\right.\right.\right.\right.\right. \right.\right.\right.\right.\right.\right.\right.\right.\right.\right. \right.\right.\right.\right.\right.\right.\right.\right. Test Plan: - Run the test input, see that it works - Run the tests, see that they work - Look at huxley screenshots (not here yet :( ) and make sure they look good Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D11602
37
Parser.js
|
@ -289,7 +289,8 @@ var delimiters = [
|
|||
"|", "\\vert", "\\|", "\\Vert",
|
||||
"\\uparrow", "\\Uparrow",
|
||||
"\\downarrow", "\\Downarrow",
|
||||
"\\updownarrow", "\\Updownarrow"
|
||||
"\\updownarrow", "\\Updownarrow",
|
||||
"."
|
||||
];
|
||||
|
||||
// Parse a single delimiter
|
||||
|
@ -429,6 +430,40 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
|||
throw new ParseError(
|
||||
"Expected delimiter after '" + nucleus.text + "'");
|
||||
}
|
||||
} else if (mode === "math" && nucleus.type === "\\left") {
|
||||
// If we see a \left, first we parse the left delimiter
|
||||
var leftDelim = this.parseDelimiter(nucleus.position, mode);
|
||||
if (leftDelim) {
|
||||
// Then, we parse an inner expression. Due to the handling of \right
|
||||
// below, this should end just before the \right
|
||||
var expression = this.parseExpression(leftDelim.position, mode);
|
||||
|
||||
// Make sure we see a \right
|
||||
var right = this.lexer.lex(expression.position, mode);
|
||||
this.expect(right, "\\right");
|
||||
|
||||
// Parse the right delimiter
|
||||
var rightDelim = this.parseDelimiter(right.position, mode);
|
||||
if (rightDelim) {
|
||||
return new ParseResult(
|
||||
new ParseNode("leftright", {
|
||||
left: leftDelim.result.value,
|
||||
right: rightDelim.result.value,
|
||||
body: expression.result
|
||||
}, mode),
|
||||
rightDelim.position);
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected delimiter after '" + right.text + "'");
|
||||
}
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected delimiter after '" + nucleus.text + "'");
|
||||
}
|
||||
} else if (mode === "math" && nucleus.type === "\\right") {
|
||||
// If we see a right, we explicitly return null to break out of the
|
||||
// parseExpression loop. The code for \left will handle the delimiter
|
||||
return null;
|
||||
} else if (nucleus.type === "\\llap" || nucleus.type === "\\rlap") {
|
||||
// If this is an llap or rlap, parse its argument and return
|
||||
var group = this.parseGroup(nucleus.position, mode);
|
||||
|
|
4
Style.js
|
@ -77,5 +77,7 @@ var cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc];
|
|||
|
||||
module.exports = {
|
||||
DISPLAY: styles[D],
|
||||
TEXT: styles[T]
|
||||
TEXT: styles[T],
|
||||
SCRIPT: styles[S],
|
||||
SCRIPTSCRIPT: styles[SS]
|
||||
};
|
||||
|
|
104
buildCommon.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
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;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
makeText: makeText,
|
||||
mathit: mathit,
|
||||
mathrm: mathrm,
|
||||
makeSpan: makeSpan,
|
||||
makeFragment: makeFragment,
|
||||
makeFontSizer: makeFontSizer
|
||||
};
|
397
buildTree.js
|
@ -2,11 +2,15 @@ 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 utils = require("./utils");
|
||||
var symbols = require("./symbols");
|
||||
var utils = require("./utils");
|
||||
|
||||
var makeSpan = buildCommon.makeSpan;
|
||||
|
||||
var buildExpression = function(expression, options, prev) {
|
||||
var groups = [];
|
||||
|
@ -18,46 +22,6 @@ var buildExpression = function(expression, options, prev) {
|
|||
return groups;
|
||||
};
|
||||
|
||||
var makeSpan = function(classes, children, color) {
|
||||
var height = 0;
|
||||
var depth = 0;
|
||||
var maxFontSize = 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;
|
||||
}
|
||||
if (children[i].maxFontSize > maxFontSize) {
|
||||
maxFontSize = children[i].maxFontSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var span = new domTree.span(
|
||||
classes, children, height, depth, maxFontSize);
|
||||
|
||||
if (color) {
|
||||
span.style.color = color;
|
||||
}
|
||||
|
||||
return span;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
var groupToType = {
|
||||
mathord: "mord",
|
||||
textord: "mord",
|
||||
|
@ -73,7 +37,8 @@ var groupToType = {
|
|||
namedfn: "mop",
|
||||
katex: "mord",
|
||||
overline: "mord",
|
||||
rule: "mord"
|
||||
rule: "mord",
|
||||
leftright: "minner"
|
||||
};
|
||||
|
||||
var getTypeOfGroup = function(group) {
|
||||
|
@ -89,7 +54,7 @@ var getTypeOfGroup = function(group) {
|
|||
} else if (group.type === "sizing") {
|
||||
return getTypeOfGroup(group.value.value);
|
||||
} else if (group.type === "delimsizing") {
|
||||
return group.value.type;
|
||||
return groupToType[group.value.type];
|
||||
} else {
|
||||
return groupToType[group.type];
|
||||
}
|
||||
|
@ -117,7 +82,7 @@ var groupTypes = {
|
|||
mathord: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mord"],
|
||||
[mathit(group.value, group.mode)],
|
||||
[buildCommon.mathit(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
@ -125,7 +90,7 @@ var groupTypes = {
|
|||
textord: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mord"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
[buildCommon.mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
@ -137,14 +102,14 @@ var groupTypes = {
|
|||
var atoms = prevAtom.value.value;
|
||||
prevAtom = atoms[atoms.length - 1];
|
||||
}
|
||||
if (!prev || utils.contains(["bin", "open", "rel", "op", "punct"],
|
||||
prevAtom.type)) {
|
||||
if (!prev || utils.contains(["mbin", "mopen", "mrel", "mop", "mpunct"],
|
||||
getTypeOfGroup(prevAtom))) {
|
||||
group.type = "ord";
|
||||
className = "mord";
|
||||
}
|
||||
return makeSpan(
|
||||
[className],
|
||||
[mathrm(group.value, group.mode)],
|
||||
[buildCommon.mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
@ -152,7 +117,7 @@ var groupTypes = {
|
|||
rel: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mrel"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
[buildCommon.mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
@ -199,13 +164,13 @@ var groupTypes = {
|
|||
|
||||
var multiplier = Style.TEXT.sizeMultiplier *
|
||||
options.style.sizeMultiplier;
|
||||
// \scriptspace is 0.5pt = 0.05em * 10pt/em
|
||||
var scriptspace = 0.05 / multiplier + "em";
|
||||
var scriptspace =
|
||||
(0.5 / fontMetrics.metrics.ptPerEm) / multiplier + "em";
|
||||
|
||||
var supsub;
|
||||
|
||||
if (!group.value.sup) {
|
||||
var fontSizer = makeFontSizer(options, submid.maxFontSize);
|
||||
var fontSizer = buildCommon.makeFontSizer(options, submid.maxFontSize);
|
||||
var subwrap = makeSpan(["msub"], [fontSizer, submid]);
|
||||
|
||||
v = Math.max(v, fontMetrics.metrics.sub1,
|
||||
|
@ -221,7 +186,7 @@ var groupTypes = {
|
|||
|
||||
supsub = makeSpan(["msupsub"], [subwrap, fixIE]);
|
||||
} else if (!group.value.sub) {
|
||||
var fontSizer = makeFontSizer(options, supmid.maxFontSize);
|
||||
var fontSizer = buildCommon.makeFontSizer(options, supmid.maxFontSize);
|
||||
var supwrap = makeSpan(["msup"], [fontSizer, supmid]);
|
||||
|
||||
u = Math.max(u, p,
|
||||
|
@ -237,7 +202,7 @@ var groupTypes = {
|
|||
|
||||
supsub = makeSpan(["msupsub"], [supwrap, fixIE]);
|
||||
} else {
|
||||
var fontSizer = makeFontSizer(options,
|
||||
var fontSizer = buildCommon.makeFontSizer(options,
|
||||
Math.max(submid.maxFontSize, supmid.maxFontSize));
|
||||
var subwrap = makeSpan(["msub"], [fontSizer, submid]);
|
||||
var supwrap = makeSpan(["msup"], [fontSizer, supmid]);
|
||||
|
@ -274,13 +239,14 @@ var groupTypes = {
|
|||
supsub = makeSpan(["msupsub"], [supwrap, subwrap, fixIE]);
|
||||
}
|
||||
|
||||
return makeSpan([getTypeOfGroup(group.value.base)], [base, supsub]);
|
||||
return makeSpan([getTypeOfGroup(group.value.base)],
|
||||
[base, supsub]);
|
||||
},
|
||||
|
||||
open: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mopen"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
[buildCommon.mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
@ -288,7 +254,7 @@ var groupTypes = {
|
|||
close: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mclose"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
[buildCommon.mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
@ -310,7 +276,7 @@ var groupTypes = {
|
|||
var denom = buildGroup(group.value.denom, options.withStyle(dstyle));
|
||||
var denomdenom = makeSpan([fstyle.reset(), dstyle.cls()], [denom])
|
||||
|
||||
var fontSizer = makeFontSizer(options,
|
||||
var fontSizer = buildCommon.makeFontSizer(options,
|
||||
Math.max(numer.maxFontSize, denom.maxFontSize));
|
||||
|
||||
var numerrow = makeSpan(["mfracnum"], [fontSizer, numernumer]);
|
||||
|
@ -358,7 +324,8 @@ var groupTypes = {
|
|||
frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
||||
frac.depth *= fstyle.sizeMultiplier / options.style.sizeMultiplier;
|
||||
|
||||
var wrap = makeSpan([options.style.reset(), fstyle.cls()], [frac]);
|
||||
var wrap = makeSpan(
|
||||
[options.style.reset(), fstyle.cls()], [frac]);
|
||||
|
||||
return makeSpan(["minner"], [
|
||||
makeSpan(["mfrac"], [wrap])
|
||||
|
@ -366,25 +333,13 @@ var groupTypes = {
|
|||
},
|
||||
|
||||
color: function(group, options, prev) {
|
||||
var els = buildExpression(
|
||||
var elements = 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);
|
||||
return new buildCommon.makeFragment(elements);
|
||||
},
|
||||
|
||||
spacing: function(group, options, prev) {
|
||||
|
@ -392,7 +347,7 @@ var groupTypes = {
|
|||
group.value === " " || group.value === "~") {
|
||||
return makeSpan(
|
||||
["mord", "mspace"],
|
||||
[mathrm(group.value, group.mode)]
|
||||
[buildCommon.mathrm(group.value, group.mode)]
|
||||
);
|
||||
} else {
|
||||
var spacingClassMap = {
|
||||
|
@ -405,7 +360,8 @@ var groupTypes = {
|
|||
"\\!": "negativethinspace"
|
||||
};
|
||||
|
||||
return makeSpan(["mord", "mspace", spacingClassMap[group.value]]);
|
||||
return makeSpan(
|
||||
["mord", "mspace", spacingClassMap[group.value]]);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -413,20 +369,22 @@ var groupTypes = {
|
|||
var inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value, options.reset())]);
|
||||
var fix = makeSpan(["fix"], []);
|
||||
return makeSpan(["llap", options.style.cls()], [inner, fix]);
|
||||
return makeSpan(
|
||||
["llap", options.style.cls()], [inner, fix]);
|
||||
},
|
||||
|
||||
rlap: function(group, options, prev) {
|
||||
var inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value, options.reset())]);
|
||||
var fix = makeSpan(["fix"], []);
|
||||
return makeSpan(["rlap", options.style.cls()], [inner, fix]);
|
||||
return makeSpan(
|
||||
["rlap", options.style.cls()], [inner, fix]);
|
||||
},
|
||||
|
||||
punct: function(group, options, prev) {
|
||||
return makeSpan(
|
||||
["mpunct"],
|
||||
[mathrm(group.value, group.mode)],
|
||||
[buildCommon.mathrm(group.value, group.mode)],
|
||||
options.getColor()
|
||||
);
|
||||
},
|
||||
|
@ -441,35 +399,41 @@ var groupTypes = {
|
|||
namedfn: function(group, options, prev) {
|
||||
var chars = [];
|
||||
for (var i = 1; i < group.value.length; i++) {
|
||||
chars.push(mathrm(group.value[i], group.mode));
|
||||
chars.push(buildCommon.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)]);
|
||||
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"], [mathrm("T", group.mode)]);
|
||||
var e = makeSpan(["e"], [mathrm("E", group.mode)]);
|
||||
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"], [mathrm("X", group.mode)]);
|
||||
var x = makeSpan(
|
||||
["x"], [buildCommon.mathrm("X", group.mode)]);
|
||||
|
||||
return makeSpan(["katex-logo"], [k, a, t, e, x], options.getColor());
|
||||
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()));
|
||||
|
||||
var fontSizer = makeFontSizer(options, innerGroup.maxFontSize);
|
||||
var fontSizer = buildCommon.makeFontSizer(options, innerGroup.maxFontSize);
|
||||
|
||||
// The theta variable in the TeXbook
|
||||
var lineWidth = fontMetrics.metrics.defaultRuleThickness;
|
||||
|
@ -518,185 +482,51 @@ var groupTypes = {
|
|||
},
|
||||
|
||||
delimsizing: function(group, options, prev) {
|
||||
var normalDelimiters = [
|
||||
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
||||
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
||||
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
|
||||
"<", ">", "\\langle", "\\rangle", "/", "\\backslash"
|
||||
];
|
||||
var delim = group.value.value;
|
||||
|
||||
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);
|
||||
|
||||
var node = makeSpan(
|
||||
[options.style.reset(), Style.TEXT.cls(),
|
||||
groupToType[group.value.type]],
|
||||
[makeSpan(
|
||||
["delimsizing", size, groupToType[group.value.type]],
|
||||
[inner], options.getColor())]);
|
||||
|
||||
var multiplier = Style.TEXT.sizeMultiplier /
|
||||
options.style.sizeMultiplier;
|
||||
|
||||
node.height *= multiplier;
|
||||
node.depth *= multiplier;
|
||||
node.maxFontSize = 1.0;
|
||||
|
||||
return node;
|
||||
} 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);
|
||||
|
||||
var node = makeSpan(
|
||||
[options.style.reset(), Style.TEXT.cls(),
|
||||
groupToType[group.value.type]],
|
||||
[makeSpan(["delimsizing", "mult"],
|
||||
inners, options.getColor())]);
|
||||
|
||||
var multiplier = Style.TEXT.sizeMultiplier /
|
||||
options.style.sizeMultiplier;
|
||||
|
||||
node.height *= multiplier;
|
||||
node.depth *= multiplier;
|
||||
node.maxFontSize = 1.0;
|
||||
|
||||
return node;
|
||||
} else {
|
||||
throw new ParseError("Illegal delimiter: '" + original + "'");
|
||||
if (delim === ".") {
|
||||
return buildCommon.makeSpan([groupToType[group.value.type]]);
|
||||
}
|
||||
|
||||
return 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) {
|
||||
|
@ -772,47 +602,6 @@ var buildGroup = function(group, options, prev) {
|
|||
}
|
||||
};
|
||||
|
||||
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", "");
|
||||
|
|
475
delimiter.js
Normal file
|
@ -0,0 +1,475 @@
|
|||
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 buildCommon = require("./buildCommon");
|
||||
var makeSpan = require("./buildCommon").makeSpan;
|
||||
|
||||
// Get the metrics for a given symbol and font, after transformation (i.e.
|
||||
// after following replacement from symbols.js)
|
||||
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 mathrmSize = function(value, size, mode) {
|
||||
return buildCommon.makeText(value, "Size" + size + "-Regular", mode);
|
||||
};
|
||||
|
||||
var styleWrap = function(delim, toStyle, options) {
|
||||
var span = makeSpan(["style-wrap", options.style.reset(), toStyle.cls()], [delim]);
|
||||
|
||||
var multiplier = toStyle.sizeMultiplier / options.style.sizeMultiplier;
|
||||
|
||||
span.height *= multiplier;
|
||||
span.depth *= multiplier;
|
||||
span.maxFontSize = toStyle.sizeMultiplier;
|
||||
|
||||
return span;
|
||||
};
|
||||
|
||||
var makeSmallDelim = function(delim, style, center, options, mode) {
|
||||
var text = buildCommon.makeText(delim, "Main-Regular", mode);
|
||||
|
||||
var span = styleWrap(text, style, options);
|
||||
|
||||
if (center) {
|
||||
var shift =
|
||||
(1 - options.style.sizeMultiplier / style.sizeMultiplier) *
|
||||
fontMetrics.metrics.axisHeight;
|
||||
|
||||
span.style.top = shift + "em";
|
||||
span.height -= shift;
|
||||
span.depth += shift;
|
||||
}
|
||||
|
||||
return span;
|
||||
};
|
||||
|
||||
var makeLargeDelim = function(delim, size, center, options, mode) {
|
||||
var inner = mathrmSize(delim, size, mode);
|
||||
|
||||
var span = styleWrap(
|
||||
makeSpan(["delimsizing", "size" + size],
|
||||
[inner], options.getColor()),
|
||||
Style.TEXT, options);
|
||||
|
||||
if (center) {
|
||||
var shift = (1 - options.style.sizeMultiplier) *
|
||||
fontMetrics.metrics.axisHeight;
|
||||
|
||||
span.style.top = shift + "em";
|
||||
span.height -= shift;
|
||||
span.depth += shift;
|
||||
}
|
||||
|
||||
return span;
|
||||
};
|
||||
|
||||
// Make an inner span with the given offset and in the given font
|
||||
var makeInner = function(symbol, offset, font, mode) {
|
||||
var sizeClass;
|
||||
if (font === "Size1-Regular") {
|
||||
sizeClass = "size1";
|
||||
} else if (font === "Size4-Regular") {
|
||||
sizeClass = "size4";
|
||||
}
|
||||
|
||||
var inner = makeSpan(
|
||||
["delimsizinginner", sizeClass],
|
||||
[makeSpan([], [buildCommon.makeText(symbol, font, mode)])]);
|
||||
|
||||
inner.style.top = offset + "em";
|
||||
inner.height -= offset;
|
||||
inner.depth += offset;
|
||||
|
||||
return inner;
|
||||
};
|
||||
|
||||
var makeStackedDelim = function(delim, heightTotal, center, options, mode) {
|
||||
// There are four parts, the top, a middle, a repeated part, and a bottom.
|
||||
var top, middle, repeat, bottom;
|
||||
top = repeat = bottom = delim;
|
||||
middle = null;
|
||||
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
|
||||
// repeats of the arrows
|
||||
if (delim === "\\uparrow") {
|
||||
repeat = bottom = "\u23d0";
|
||||
} else if (delim === "\\Uparrow") {
|
||||
repeat = bottom = "\u2016";
|
||||
} else if (delim === "\\downarrow") {
|
||||
top = repeat = "\u23d0";
|
||||
} else if (delim === "\\Downarrow") {
|
||||
top = repeat = "\u2016";
|
||||
} else if (delim === "\\updownarrow") {
|
||||
top = "\\uparrow";
|
||||
repeat = "\u23d0";
|
||||
bottom = "\\downarrow";
|
||||
} else if (delim === "\\Updownarrow") {
|
||||
top = "\\Uparrow";
|
||||
repeat = "\u2016";
|
||||
bottom = "\\Downarrow";
|
||||
|
||||
// For some reason, the sizes of this one delimiter don't work out
|
||||
// right, so we shrink it a bit to make it now add an extraneous
|
||||
// repeating part
|
||||
if (height + depth <= 1.21) {
|
||||
height -= 0.01;
|
||||
depth -= 0.01;
|
||||
}
|
||||
} else if (delim === "|" || delim === "\\vert") {
|
||||
overlap = true;
|
||||
} else if (delim === "\\|" || delim === "\\Vert") {
|
||||
overlap = true;
|
||||
} else if (delim === "[" || delim === "\\lbrack") {
|
||||
top = "\u23a1";
|
||||
repeat = "\u23a2";
|
||||
bottom = "\u23a3";
|
||||
font = "Size4-Regular";
|
||||
overlap = true;
|
||||
} else if (delim === "]" || delim === "\\rbrack") {
|
||||
top = "\u23a4";
|
||||
repeat = "\u23a5";
|
||||
bottom = "\u23a6";
|
||||
font = "Size4-Regular";
|
||||
overlap = true;
|
||||
} else if (delim === "\\lfloor") {
|
||||
repeat = top = "\u23a2";
|
||||
bottom = "\u23a3";
|
||||
font = "Size4-Regular";
|
||||
overlap = true;
|
||||
} else if (delim === "\\lceil") {
|
||||
top = "\u23a1";
|
||||
repeat = bottom = "\u23a2";
|
||||
font = "Size4-Regular";
|
||||
overlap = true;
|
||||
} else if (delim === "\\rfloor") {
|
||||
repeat = top = "\u23a5";
|
||||
bottom = "\u23a6";
|
||||
font = "Size4-Regular";
|
||||
overlap = true;
|
||||
} else if (delim === "\\rceil") {
|
||||
top = "\u23a4";
|
||||
repeat = bottom = "\u23a5";
|
||||
font = "Size4-Regular";
|
||||
overlap = true;
|
||||
} else if (delim === "(") {
|
||||
top = "\u239b";
|
||||
repeat = "\u239c";
|
||||
bottom = "\u239d";
|
||||
font = "Size4-Regular";
|
||||
overlap = true;
|
||||
} else if (delim === ")") {
|
||||
top = "\u239e";
|
||||
repeat = "\u239f";
|
||||
bottom = "\u23a0";
|
||||
font = "Size4-Regular";
|
||||
overlap = true;
|
||||
} else if (delim === "\\{" || delim === "\\lbrace") {
|
||||
top = "\u23a7";
|
||||
middle = "\u23a8";
|
||||
bottom = "\u23a9";
|
||||
repeat = "\u23aa";
|
||||
font = "Size4-Regular";
|
||||
} else if (delim === "\\}" || delim === "\\rbrace") {
|
||||
top = "\u23ab";
|
||||
middle = "\u23ac";
|
||||
bottom = "\u23ad";
|
||||
repeat = "\u23aa";
|
||||
font = "Size4-Regular";
|
||||
}
|
||||
|
||||
// Get the metrics of the three sections
|
||||
var topMetrics = getMetrics(top, font);
|
||||
var topHeightTotal = topMetrics.height + topMetrics.depth;
|
||||
var repeatMetrics = getMetrics(repeat, font);
|
||||
var repeatHeightTotal = repeatMetrics.height + repeatMetrics.depth;
|
||||
var bottomMetrics = getMetrics(bottom, font);
|
||||
var bottomHeightTotal = bottomMetrics.height + bottomMetrics.depth;
|
||||
var middleMetrics, middleHeightTotal;
|
||||
if (middle !== null) {
|
||||
middleMetrics = getMetrics(middle, font);
|
||||
middleHeightTotal = middleMetrics.height + middleMetrics.depth;
|
||||
}
|
||||
|
||||
var realHeightTotal = topHeightTotal + bottomHeightTotal;
|
||||
if (middle !== null) {
|
||||
realHeightTotal += middleHeightTotal;
|
||||
}
|
||||
|
||||
while (realHeightTotal < heightTotal) {
|
||||
realHeightTotal += repeatHeightTotal;
|
||||
if (middle !== null) {
|
||||
realHeightTotal += repeatHeightTotal;
|
||||
}
|
||||
}
|
||||
|
||||
var axisHeight = fontMetrics.metrics.axisHeight;
|
||||
if (center) {
|
||||
axisHeight *= options.style.sizeMultiplier;
|
||||
}
|
||||
var height = realHeightTotal / 2 + axisHeight;
|
||||
var depth = realHeightTotal / 2 - axisHeight;
|
||||
|
||||
// Keep a list of the inner spans
|
||||
var inners = [];
|
||||
|
||||
// Add the top symbol
|
||||
inners.push(
|
||||
makeInner(top, topMetrics.height - height, font, mode));
|
||||
|
||||
if (middle === null) {
|
||||
var repeatHeight = realHeightTotal - topHeightTotal - bottomHeightTotal;
|
||||
var symbolCount = Math.ceil(repeatHeight / repeatHeightTotal);
|
||||
|
||||
var overlapAmount;
|
||||
if (overlap) {
|
||||
// 2 * overlapAmount + repeatHeight =
|
||||
// (symbolCount - 1) * (repeatHeightTotal - overlapAmount) +
|
||||
// repeatHeightTotal
|
||||
overlapAmount = (symbolCount * repeatHeightTotal -
|
||||
repeatHeight) / (symbolCount + 1);
|
||||
} else {
|
||||
overlapAmount = 0;
|
||||
}
|
||||
|
||||
// Add repeat symbols until there's only space for the bottom symbol
|
||||
var currHeight = height - topHeightTotal + overlapAmount;
|
||||
for (var i = 0; i < symbolCount; i++) {
|
||||
inners.push(
|
||||
makeInner(repeat,
|
||||
repeatMetrics.height - currHeight, font, mode));
|
||||
currHeight -= repeatHeightTotal - overlapAmount;
|
||||
}
|
||||
} else {
|
||||
// When there is a middle bit, we need the middle part and two repeated
|
||||
// sections
|
||||
|
||||
// Calculate the number of symbols needed for the top and bottom
|
||||
// repeated parts
|
||||
var topRepeatHeight =
|
||||
realHeightTotal / 2 - topHeightTotal - middleHeightTotal / 2;
|
||||
var topSymbolCount = Math.ceil(topRepeatHeight / repeatHeightTotal);
|
||||
|
||||
var bottomRepeatHeight =
|
||||
realHeightTotal / 2 - topHeightTotal - middleHeightTotal / 2;
|
||||
var bottomSymbolCount =
|
||||
Math.ceil(bottomRepeatHeight / repeatHeightTotal);
|
||||
|
||||
// Add the top repeated part
|
||||
var currHeight = height - topHeightTotal;
|
||||
for (var i = 0; i < topSymbolCount; i++) {
|
||||
inners.push(
|
||||
makeInner(repeat,
|
||||
repeatMetrics.height - currHeight, font, mode));
|
||||
currHeight -= repeatHeightTotal;
|
||||
}
|
||||
|
||||
// Add the middle piece
|
||||
var midPoint = realHeightTotal / 2 - depth;
|
||||
inners.push(
|
||||
makeInner(middle,
|
||||
middleMetrics.height - midPoint - middleHeightTotal / 2,
|
||||
font, mode));
|
||||
|
||||
// Add the bottom repeated part
|
||||
currHeight = midPoint - middleHeightTotal / 2;
|
||||
for (var i = 0; i < bottomSymbolCount; i++) {
|
||||
inners.push(
|
||||
makeInner(repeat,
|
||||
repeatMetrics.height - currHeight, font, mode));
|
||||
currHeight -= repeatHeightTotal;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the bottom symbol
|
||||
inners.push(
|
||||
makeInner(bottom, depth - bottomMetrics.depth, font, mode));
|
||||
|
||||
var fixIE = makeSpan(["fix-ie"], [new domTree.textNode("\u00a0")]);
|
||||
inners.push(fixIE);
|
||||
|
||||
return styleWrap(
|
||||
makeSpan(["delimsizing", "mult"], inners, options.getColor()),
|
||||
Style.TEXT, options);
|
||||
};
|
||||
|
||||
var normalDelimiters = [
|
||||
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
||||
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
||||
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
|
||||
"<", ">", "\\langle", "\\rangle", "/", "\\backslash"
|
||||
];
|
||||
|
||||
var stackDelimiters = [
|
||||
"\\uparrow", "\\downarrow", "\\updownarrow",
|
||||
"\\Uparrow", "\\Downarrow", "\\Updownarrow",
|
||||
"|", "\\|", "\\vert", "\\Vert"
|
||||
];
|
||||
|
||||
var onlyNormalDelimiters = [
|
||||
"<", ">", "\\langle", "\\rangle", "/", "\\backslash"
|
||||
];
|
||||
|
||||
// Metrics of the different sizes. Found by looking at TeX's output of
|
||||
// $\bigl| \Bigl| \biggl| \Biggl| \showlists$
|
||||
var sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0];
|
||||
|
||||
var makeSizedDelim = function(delim, size, options, mode) {
|
||||
if (delim === "<") {
|
||||
delim = "\\langle";
|
||||
} else if (delim === ">") {
|
||||
delim = "\\rangle";
|
||||
}
|
||||
|
||||
var retDelim;
|
||||
|
||||
if (utils.contains(normalDelimiters, delim)) {
|
||||
return makeLargeDelim(delim, size, false, options, mode);
|
||||
} else if (utils.contains(stackDelimiters, delim)) {
|
||||
return makeStackedDelim(
|
||||
delim, sizeToMaxHeight[size], false, options, mode);
|
||||
} else {
|
||||
throw new ParseError("Illegal delimiter: '" + delim + "'");
|
||||
}
|
||||
};
|
||||
|
||||
var normalDelimiterSequence = [
|
||||
{type: "small", style: Style.SCRIPTSCRIPT},
|
||||
{type: "small", style: Style.SCRIPT},
|
||||
{type: "small", style: Style.TEXT},
|
||||
{type: "large", size: 1},
|
||||
{type: "large", size: 2},
|
||||
{type: "large", size: 3},
|
||||
{type: "large", size: 4}
|
||||
];
|
||||
|
||||
var stackAlwaysDelimiterSequence = [
|
||||
{type: "small", style: Style.SCRIPTSCRIPT},
|
||||
{type: "small", style: Style.SCRIPT},
|
||||
{type: "small", style: Style.TEXT},
|
||||
{type: "stack"}
|
||||
];
|
||||
|
||||
var stackLargeDelimiterSequence = [
|
||||
{type: "small", style: Style.SCRIPTSCRIPT},
|
||||
{type: "small", style: Style.SCRIPT},
|
||||
{type: "small", style: Style.TEXT},
|
||||
{type: "large", size: 1},
|
||||
{type: "large", size: 2},
|
||||
{type: "large", size: 3},
|
||||
{type: "large", size: 4},
|
||||
{type: "stack"}
|
||||
];
|
||||
|
||||
var delimTypeToFont = function(type) {
|
||||
if (type.type === "small") {
|
||||
return "Main-Regular";
|
||||
} else if (type.type === "large") {
|
||||
return "Size" + type.size + "-Regular";
|
||||
} else if (type.type === "stack") {
|
||||
return "Size4-Regular";
|
||||
}
|
||||
};
|
||||
|
||||
var traverseSequence = function(delim, height, sequence, options) {
|
||||
// Here, we choose the index we should start at in the sequences. In smaller
|
||||
// sizes (which correspond to larger numbers in style.size) we start earlier
|
||||
// in the sequence. Thus, scriptscript starts at index 3-3=0, script starts
|
||||
// at index 3-2=1, text starts at 3-1=2, and display starts at min(2,3-0)=2
|
||||
var start = Math.min(2, 3 - options.style.size);
|
||||
for (var i = start; i < sequence.length; i++) {
|
||||
if (sequence[i].type === "stack") {
|
||||
// This is always the last delimiter, so we just break the loop now.
|
||||
break;
|
||||
}
|
||||
|
||||
var metrics = getMetrics(delim, delimTypeToFont(sequence[i]));
|
||||
|
||||
var heightDepth = metrics.height + metrics.depth;
|
||||
|
||||
if (sequence[i].type === "small") {
|
||||
heightDepth *= sequence[i].style.sizeMultiplier;
|
||||
}
|
||||
|
||||
if (heightDepth > height) {
|
||||
return sequence[i];
|
||||
}
|
||||
}
|
||||
|
||||
return sequence[sequence.length - 1];
|
||||
};
|
||||
|
||||
var makeCustomSizedDelim = function(delim, height, center, options, mode) {
|
||||
if (delim === "<") {
|
||||
delim = "\\langle";
|
||||
} else if (delim === ">") {
|
||||
delim = "\\rangle";
|
||||
}
|
||||
|
||||
var sequence;
|
||||
if (utils.contains(onlyNormalDelimiters, delim)) {
|
||||
sequence = normalDelimiterSequence;
|
||||
} else if (utils.contains(normalDelimiters, delim)) {
|
||||
sequence = stackLargeDelimiterSequence;
|
||||
} else {
|
||||
sequence = stackAlwaysDelimiterSequence;
|
||||
}
|
||||
|
||||
var delimType = traverseSequence(delim, height, sequence, options);
|
||||
|
||||
if (delimType.type === "small") {
|
||||
return makeSmallDelim(delim, delimType.style, center, options, mode);
|
||||
} else if (delimType.type === "large") {
|
||||
return makeLargeDelim(delim, delimType.size, center, options, mode);
|
||||
} else if (delimType.type === "stack") {
|
||||
return makeStackedDelim(delim, height, center, options, mode);
|
||||
}
|
||||
};
|
||||
|
||||
var makeLeftRightDelim = function(delim, height, depth, options, mode) {
|
||||
var axisHeight =
|
||||
fontMetrics.metrics.axisHeight * options.style.sizeMultiplier;
|
||||
|
||||
// Taken from TeX source, tex.web, function make_left_right
|
||||
var delimiterFactor = 901;
|
||||
var delimiterExtend = 5.0 / fontMetrics.metrics.ptPerEm;
|
||||
|
||||
var maxDistFromAxis = Math.max(
|
||||
height - axisHeight, depth + axisHeight);
|
||||
|
||||
var totalHeight = Math.max(
|
||||
// In real TeX, calculations are done using integral values which are
|
||||
// 65536 per pt, or 655360 per em. So, the division here truncates in
|
||||
// TeX but doesn't here, producing different results. If we wanted to
|
||||
// exactly match TeX's calculation, we could do
|
||||
// Math.floor(655360 * maxDistFromAxis / 500) *
|
||||
// delimiterFactor / 655360
|
||||
// (To see the difference, compare
|
||||
// x^{x^{\left(\rule{0.1em}{0.68em}\right)}}
|
||||
// in TeX and KaTeX)
|
||||
maxDistFromAxis / 500 * delimiterFactor,
|
||||
2 * maxDistFromAxis - delimiterExtend);
|
||||
|
||||
return makeCustomSizedDelim(delim, totalHeight, true, options, mode);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
sizedDelim: makeSizedDelim,
|
||||
customSizedDelim: makeCustomSizedDelim,
|
||||
leftRightDelim: makeLeftRightDelim
|
||||
};
|
|
@ -37,10 +37,11 @@ span.prototype.toDOM = function() {
|
|||
return span;
|
||||
};
|
||||
|
||||
function documentFragment(children, height, depth) {
|
||||
function documentFragment(children, height, depth, maxFontSize) {
|
||||
this.children = children || [];
|
||||
this.height = height || 0;
|
||||
this.depth = depth || 0;
|
||||
this.maxFontSize = maxFontSize || 0;
|
||||
}
|
||||
|
||||
documentFragment.prototype.toDOM = function() {
|
||||
|
|
|
@ -53,9 +53,9 @@ def main():
|
|||
|
||||
tfm_char = font_name_to_tfm[font].get_char_metrics(tex_char_num)
|
||||
|
||||
height = round(tfm_char.height + yshift / 1000.0, 3)
|
||||
depth = round(tfm_char.depth - yshift / 1000.0, 3)
|
||||
italic = round(tfm_char.italic_correction, 3)
|
||||
height = round(tfm_char.height + yshift / 1000.0, 5)
|
||||
depth = round(tfm_char.depth - yshift / 1000.0, 5)
|
||||
italic = round(tfm_char.italic_correction, 5)
|
||||
|
||||
families[family][char_num] = {
|
||||
'height': height,
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
<link href="main.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<input type="text" value="\blue\dfrac{\frac{\phi^2}{3}-G_a^{x^3}}{2\times3+4}+\orange\dfrac{(x^2+y^2)^\frac{1}{2}}{\tan\psi^\tau+2/3}" id="input" />
|
||||
<input type="text"
|
||||
value="(\left( x \right) \left( x^2 \right) \left( \frac{a}{b} \right) \left( \frac{a^2}{b} \right) \left( \dfrac{a}{b} \right) \left( \dfrac{a^2}{b} \right)"
|
||||
id="input" />
|
||||
<div id="math"></div>
|
||||
<input id="permalink" type="button" value="permalink">
|
||||
<script src="main.js" type="text/javascript"></script>
|
||||
|
|
|
@ -39,6 +39,11 @@ big parens
|
|||
table-layout: fixed;
|
||||
}
|
||||
|
||||
// This value is also used in fontMetrics.js, if you change it make sure the
|
||||
// values match.
|
||||
@ptperem: 10.0;
|
||||
@nulldelimiterspace: 1.2em / @ptperem;
|
||||
|
||||
@thinspace: 0.16667em;
|
||||
@mediumspace: 0.22222em;
|
||||
@thickspace: 0.27778em;
|
||||
|
@ -162,6 +167,10 @@ big parens
|
|||
.reset-scriptscriptstyle.scriptstyle { font-size: 1.4em; }
|
||||
.reset-scriptscriptstyle.scriptscriptstyle { font-size: 1em; }
|
||||
|
||||
.style-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.baseline-align-hack-outer() {
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -392,12 +401,18 @@ big parens
|
|||
.baseline-align-hack-middle;
|
||||
position: relative;
|
||||
|
||||
&.size1 {
|
||||
> span {
|
||||
font-family: KaTeX_Size1;
|
||||
}
|
||||
&.size1 > span {
|
||||
font-family: Katex_Size1;
|
||||
}
|
||||
&.size4 > span {
|
||||
font-family: Katex_Size4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nulldelimiter {
|
||||
display: inline-block;
|
||||
width: @nulldelimiterspace;
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
@ -131,9 +131,27 @@
|
|||
"url": "http://localhost:7936/test/huxley/test.html?m=\\rule{1em}{0.5em}\\rule{1ex}{2ex}\\rule{1em}{1ex}\\rule{1em}{0.431ex}"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "LeftRight",
|
||||
"screenSize": [1024, 768],
|
||||
"url": "http://localhost:7936/test/huxley/test.html?m=\\left( x^2 \\right) \\left\\{ x^{x^{x^{x^x}}} \\right."
|
||||
},
|
||||
|
||||
{
|
||||
"name": "LeftRightStyleSizing",
|
||||
"screenSize": [1024, 768],
|
||||
"url": "http://localhost:7936/test/huxley/test.html?m=+\\left\\{\\rule{0.1em}{1em}\\right.x^{+\\left\\{\\rule{0.1em}{1em}\\right.x^{+\\left\\{\\rule{0.1em}{1em}\\right.}}"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "PrimeSpacing",
|
||||
"screenSize": [1024, 768],
|
||||
"url": "http://localhost:7936/test/huxley/test.html?m=f'+f_2'+f^{f'}"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "NullDelimiterInteraction",
|
||||
"screenSize": [1024, 768],
|
||||
"url": "http://localhost:7936/test/huxley/test.html?m=a \\bigl. + 2 \\quad \\left. + a \\right)"
|
||||
}
|
||||
]
|
||||
|
|
BIN
test/huxley/LeftRight.hux/firefox-1.png
Normal file
After Width: | Height: | Size: 17 KiB |
5
test/huxley/LeftRight.hux/record.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
[
|
||||
{
|
||||
"action": "screenshot"
|
||||
}
|
||||
]
|
BIN
test/huxley/LeftRightStyleSizing.hux/firefox-1.png
Normal file
After Width: | Height: | Size: 15 KiB |
5
test/huxley/LeftRightStyleSizing.hux/record.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
[
|
||||
{
|
||||
"action": "screenshot"
|
||||
}
|
||||
]
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
BIN
test/huxley/NullDelimiterInteraction.hux/firefox-1.png
Normal file
After Width: | Height: | Size: 12 KiB |
5
test/huxley/NullDelimiterInteraction.hux/record.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
[
|
||||
{
|
||||
"action": "screenshot"
|
||||
}
|
||||
]
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
@ -739,3 +739,62 @@ describe("A rule parser", function() {
|
|||
expect(hardNumberParse.value.height.number).toBeCloseTo(2.45);
|
||||
});
|
||||
});
|
||||
|
||||
describe("A left/right parser", function() {
|
||||
var normalLeftRight = "\\left( \\dfrac{x}{y} \\right)";
|
||||
var emptyRight = "\\left( \\dfrac{x}{y} \\right.";
|
||||
|
||||
it("should not fail", function() {
|
||||
expect(function() {
|
||||
parseTree(normalLeftRight);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should produce a leftright", function() {
|
||||
var parse = parseTree(normalLeftRight)[0];
|
||||
|
||||
expect(parse.type).toMatch("leftright");
|
||||
expect(parse.value.left).toMatch("\\(");
|
||||
expect(parse.value.right).toMatch("\\)");
|
||||
});
|
||||
|
||||
it("should error when it is mismatched", function() {
|
||||
var unmatchedLeft = "\\left( \\dfrac{x}{y}";
|
||||
var unmatchedRight = "\\dfrac{x}{y} \\right)";
|
||||
|
||||
expect(function() {
|
||||
parseTree(unmatchedLeft);
|
||||
}).toThrow();
|
||||
|
||||
expect(function() {
|
||||
parseTree(unmatchedRight);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it("should error when braces are mismatched", function() {
|
||||
var unmatched = "{ \\left( \\dfrac{x}{y} } \\right)";
|
||||
expect(function() {
|
||||
parseTree(unmatched);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it("should error when non-delimiters are provided", function() {
|
||||
var nonDelimiter = "\\left$ \\dfrac{x}{y} \\right)";
|
||||
expect(function() {
|
||||
parseTree(nonDelimiter);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it("should parse the empty '.' delimiter", function() {
|
||||
expect(function() {
|
||||
parseTree(emptyRight);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should parse the '.' delimiter with normal sizes", function() {
|
||||
var normalEmpty = "\\Bigl .";
|
||||
expect(function() {
|
||||
parseTree(normalEmpty);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
|