|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 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", |
|
inner: "minner", |
|
genfrac: "mord", |
|
array: "mord", |
|
spacing: "mord", |
|
punct: "mpunct", |
|
ordgroup: "mord", |
|
op: "mop", |
|
katex: "mord", |
|
overline: "mord", |
|
underline: "mord", |
|
rule: "mord", |
|
leftright: "minner", |
|
sqrt: "mord", |
|
accent: "mord", |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var getTypeOfGroup = function(group) { |
|
if (group == null) { |
|
|
|
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 shouldHandleSupSub = function(group, options) { |
|
if (!group) { |
|
return false; |
|
} else if (group.type === "op") { |
|
|
|
|
|
return group.value.limits && |
|
(options.style.size === Style.DISPLAY.size || |
|
group.value.alwaysHandleSupSub); |
|
} else if (group.type === "accent") { |
|
return isCharacterBox(group.value.base); |
|
} else { |
|
return null; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
var getBaseElem = function(group) { |
|
if (!group) { |
|
return false; |
|
} else if (group.type === "ordgroup") { |
|
if (group.value.length === 1) { |
|
return getBaseElem(group.value[0]); |
|
} else { |
|
return group; |
|
} |
|
} else if (group.type === "color") { |
|
if (group.value.value.length === 1) { |
|
return getBaseElem(group.value.value[0]); |
|
} else { |
|
return group; |
|
} |
|
} else { |
|
return group; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
var isCharacterBox = function(group) { |
|
var baseElem = getBaseElem(group); |
|
|
|
|
|
return baseElem.type === "mathord" || |
|
baseElem.type === "textord" || |
|
baseElem.type === "bin" || |
|
baseElem.type === "rel" || |
|
baseElem.type === "inner" || |
|
baseElem.type === "open" || |
|
baseElem.type === "close" || |
|
baseElem.type === "punct"; |
|
}; |
|
|
|
var makeNullDelimiter = function(options) { |
|
return makeSpan([ |
|
"sizing", "reset-" + options.size, "size5", |
|
options.style.reset(), Style.TEXT.cls(), |
|
"nulldelimiter", |
|
]); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
var groupTypes = {}; |
|
|
|
groupTypes.mathord = function(group, options, prev) { |
|
return buildCommon.makeOrd(group, options, "mathord"); |
|
}; |
|
|
|
groupTypes.textord = function(group, options, prev) { |
|
return buildCommon.makeOrd(group, options, "textord"); |
|
}; |
|
|
|
groupTypes.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 = "textord"; |
|
className = "mord"; |
|
} |
|
|
|
return buildCommon.mathsym( |
|
group.value, group.mode, options.getColor(), [className]); |
|
}; |
|
|
|
groupTypes.rel = function(group, options, prev) { |
|
return buildCommon.mathsym( |
|
group.value, group.mode, options.getColor(), ["mrel"]); |
|
}; |
|
|
|
groupTypes.open = function(group, options, prev) { |
|
return buildCommon.mathsym( |
|
group.value, group.mode, options.getColor(), ["mopen"]); |
|
}; |
|
|
|
groupTypes.close = function(group, options, prev) { |
|
return buildCommon.mathsym( |
|
group.value, group.mode, options.getColor(), ["mclose"]); |
|
}; |
|
|
|
groupTypes.inner = function(group, options, prev) { |
|
return buildCommon.mathsym( |
|
group.value, group.mode, options.getColor(), ["minner"]); |
|
}; |
|
|
|
groupTypes.punct = function(group, options, prev) { |
|
return buildCommon.mathsym( |
|
group.value, group.mode, options.getColor(), ["mpunct"]); |
|
}; |
|
|
|
groupTypes.ordgroup = function(group, options, prev) { |
|
return makeSpan( |
|
["mord", options.style.cls()], |
|
buildExpression(group.value, options.reset()) |
|
); |
|
}; |
|
|
|
groupTypes.text = function(group, options, prev) { |
|
return makeSpan(["text", "mord", options.style.cls()], |
|
buildExpression(group.value.body, options.reset())); |
|
}; |
|
|
|
groupTypes.color = function(group, options, prev) { |
|
var elements = buildExpression( |
|
group.value.value, |
|
options.withColor(group.value.color), |
|
prev |
|
); |
|
|
|
|
|
|
|
|
|
|
|
return new buildCommon.makeFragment(elements); |
|
}; |
|
|
|
groupTypes.supsub = function(group, options, prev) { |
|
|
|
|
|
|
|
|
|
|
|
if (shouldHandleSupSub(group.value.base, options)) { |
|
return groupTypes[group.value.base.type](group, options, prev); |
|
} |
|
|
|
var base = buildGroup(group.value.base, options.reset()); |
|
var supmid; |
|
var submid; |
|
var sup; |
|
var sub; |
|
|
|
if (group.value.sup) { |
|
sup = buildGroup(group.value.sup, |
|
options.withStyle(options.style.sup())); |
|
supmid = makeSpan( |
|
[options.style.reset(), options.style.sup().cls()], [sup]); |
|
} |
|
|
|
if (group.value.sub) { |
|
sub = buildGroup(group.value.sub, |
|
options.withStyle(options.style.sub())); |
|
submid = makeSpan( |
|
[options.style.reset(), options.style.sub().cls()], [sub]); |
|
} |
|
|
|
|
|
var supShift; |
|
var subShift; |
|
if (isCharacterBox(group.value.base)) { |
|
supShift = 0; |
|
subShift = 0; |
|
} else { |
|
supShift = base.height - fontMetrics.metrics.supDrop; |
|
subShift = base.depth + fontMetrics.metrics.subDrop; |
|
} |
|
|
|
|
|
var minSupShift; |
|
if (options.style === Style.DISPLAY) { |
|
minSupShift = fontMetrics.metrics.sup1; |
|
} else if (options.style.cramped) { |
|
minSupShift = fontMetrics.metrics.sup3; |
|
} else { |
|
minSupShift = 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) { |
|
|
|
subShift = Math.max( |
|
subShift, fontMetrics.metrics.sub1, |
|
sub.height - 0.8 * fontMetrics.metrics.xHeight); |
|
|
|
supsub = buildCommon.makeVList([ |
|
{type: "elem", elem: submid}, |
|
], "shift", subShift, options); |
|
|
|
supsub.children[0].style.marginRight = scriptspace; |
|
|
|
|
|
|
|
|
|
if (base instanceof domTree.symbolNode) { |
|
supsub.children[0].style.marginLeft = -base.italic + "em"; |
|
} |
|
} else if (!group.value.sub) { |
|
|
|
supShift = Math.max(supShift, minSupShift, |
|
sup.depth + 0.25 * fontMetrics.metrics.xHeight); |
|
|
|
supsub = buildCommon.makeVList([ |
|
{type: "elem", elem: supmid}, |
|
], "shift", -supShift, options); |
|
|
|
supsub.children[0].style.marginRight = scriptspace; |
|
} else { |
|
supShift = Math.max( |
|
supShift, minSupShift, |
|
sup.depth + 0.25 * fontMetrics.metrics.xHeight); |
|
subShift = Math.max(subShift, fontMetrics.metrics.sub2); |
|
|
|
var ruleWidth = fontMetrics.metrics.defaultRuleThickness; |
|
|
|
|
|
if ((supShift - sup.depth) - (sub.height - subShift) < |
|
4 * ruleWidth) { |
|
subShift = 4 * ruleWidth - (supShift - sup.depth) + sub.height; |
|
var psi = 0.8 * fontMetrics.metrics.xHeight - |
|
(supShift - sup.depth); |
|
if (psi > 0) { |
|
supShift += psi; |
|
subShift -= psi; |
|
} |
|
} |
|
|
|
supsub = buildCommon.makeVList([ |
|
{type: "elem", elem: submid, shift: subShift}, |
|
{type: "elem", elem: supmid, shift: -supShift}, |
|
], "individualShift", null, options); |
|
|
|
|
|
if (base instanceof domTree.symbolNode) { |
|
supsub.children[0].style.marginLeft = -base.italic + "em"; |
|
} |
|
|
|
supsub.children[0].style.marginRight = scriptspace; |
|
supsub.children[1].style.marginRight = scriptspace; |
|
} |
|
|
|
return makeSpan([getTypeOfGroup(group.value.base)], |
|
[base, supsub]); |
|
}; |
|
|
|
groupTypes.genfrac = function(group, options, prev) { |
|
|
|
|
|
|
|
var fstyle = options.style; |
|
if (group.value.size === "display") { |
|
fstyle = Style.DISPLAY; |
|
} else if (group.value.size === "text") { |
|
fstyle = Style.TEXT; |
|
} |
|
|
|
var nstyle = fstyle.fracNum(); |
|
var dstyle = fstyle.fracDen(); |
|
|
|
var numer = buildGroup(group.value.numer, options.withStyle(nstyle)); |
|
var numerreset = makeSpan([fstyle.reset(), nstyle.cls()], [numer]); |
|
|
|
var denom = buildGroup(group.value.denom, options.withStyle(dstyle)); |
|
var denomreset = makeSpan([fstyle.reset(), dstyle.cls()], [denom]); |
|
|
|
var ruleWidth; |
|
if (group.value.hasBarLine) { |
|
ruleWidth = fontMetrics.metrics.defaultRuleThickness / |
|
options.style.sizeMultiplier; |
|
} else { |
|
ruleWidth = 0; |
|
} |
|
|
|
|
|
var numShift; |
|
var clearance; |
|
var denomShift; |
|
if (fstyle.size === Style.DISPLAY.size) { |
|
numShift = fontMetrics.metrics.num1; |
|
if (ruleWidth > 0) { |
|
clearance = 3 * ruleWidth; |
|
} else { |
|
clearance = 7 * fontMetrics.metrics.defaultRuleThickness; |
|
} |
|
denomShift = fontMetrics.metrics.denom1; |
|
} else { |
|
if (ruleWidth > 0) { |
|
numShift = fontMetrics.metrics.num2; |
|
clearance = ruleWidth; |
|
} else { |
|
numShift = fontMetrics.metrics.num3; |
|
clearance = 3 * fontMetrics.metrics.defaultRuleThickness; |
|
} |
|
denomShift = fontMetrics.metrics.denom2; |
|
} |
|
|
|
var frac; |
|
if (ruleWidth === 0) { |
|
|
|
var candiateClearance = |
|
(numShift - numer.depth) - (denom.height - denomShift); |
|
if (candiateClearance < clearance) { |
|
numShift += 0.5 * (clearance - candiateClearance); |
|
denomShift += 0.5 * (clearance - candiateClearance); |
|
} |
|
|
|
frac = buildCommon.makeVList([ |
|
{type: "elem", elem: denomreset, shift: denomShift}, |
|
{type: "elem", elem: numerreset, shift: -numShift}, |
|
], "individualShift", null, options); |
|
} else { |
|
|
|
var axisHeight = fontMetrics.metrics.axisHeight; |
|
|
|
if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth) < |
|
clearance) { |
|
numShift += |
|
clearance - ((numShift - numer.depth) - |
|
(axisHeight + 0.5 * ruleWidth)); |
|
} |
|
|
|
if ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift) < |
|
clearance) { |
|
denomShift += |
|
clearance - ((axisHeight - 0.5 * ruleWidth) - |
|
(denom.height - denomShift)); |
|
} |
|
|
|
var mid = makeSpan( |
|
[options.style.reset(), Style.TEXT.cls(), "frac-line"]); |
|
|
|
|
|
mid.height = ruleWidth; |
|
|
|
var midShift = -(axisHeight - 0.5 * ruleWidth); |
|
|
|
frac = buildCommon.makeVList([ |
|
{type: "elem", elem: denomreset, shift: denomShift}, |
|
{type: "elem", elem: mid, shift: midShift}, |
|
{type: "elem", elem: numerreset, shift: -numShift}, |
|
], "individualShift", null, options); |
|
} |
|
|
|
|
|
|
|
frac.height *= fstyle.sizeMultiplier / options.style.sizeMultiplier; |
|
frac.depth *= fstyle.sizeMultiplier / options.style.sizeMultiplier; |
|
|
|
|
|
var delimSize; |
|
if (fstyle.size === Style.DISPLAY.size) { |
|
delimSize = fontMetrics.metrics.delim1; |
|
} else { |
|
delimSize = fontMetrics.metrics.getDelim2(fstyle); |
|
} |
|
|
|
var leftDelim; |
|
var rightDelim; |
|
if (group.value.leftDelim == null) { |
|
leftDelim = makeNullDelimiter(options); |
|
} else { |
|
leftDelim = delimiter.customSizedDelim( |
|
group.value.leftDelim, delimSize, true, |
|
options.withStyle(fstyle), group.mode); |
|
} |
|
if (group.value.rightDelim == null) { |
|
rightDelim = makeNullDelimiter(options); |
|
} else { |
|
rightDelim = delimiter.customSizedDelim( |
|
group.value.rightDelim, delimSize, true, |
|
options.withStyle(fstyle), group.mode); |
|
} |
|
|
|
return makeSpan( |
|
["mord", options.style.reset(), fstyle.cls()], |
|
[leftDelim, makeSpan(["mfrac"], [frac]), rightDelim], |
|
options.getColor()); |
|
}; |
|
|
|
groupTypes.array = function(group, options, prev) { |
|
var r; |
|
var c; |
|
var nr = group.value.body.length; |
|
var nc = 0; |
|
var body = new Array(nr); |
|
|
|
|
|
var pt = 1 / fontMetrics.metrics.ptPerEm; |
|
var arraycolsep = 5 * pt; |
|
|
|
|
|
var baselineskip = 12 * pt; |
|
|
|
|
|
var arraystretch = utils.deflt(group.value.arraystretch, 1); |
|
var arrayskip = arraystretch * baselineskip; |
|
var arstrutHeight = 0.7 * arrayskip; |
|
var arstrutDepth = 0.3 * arrayskip; |
|
|
|
var totalHeight = 0; |
|
for (r = 0; r < group.value.body.length; ++r) { |
|
var inrow = group.value.body[r]; |
|
var height = arstrutHeight; |
|
var depth = arstrutDepth; |
|
|
|
if (nc < inrow.length) { |
|
nc = inrow.length; |
|
} |
|
|
|
var outrow = new Array(inrow.length); |
|
for (c = 0; c < inrow.length; ++c) { |
|
var elt = buildGroup(inrow[c], options); |
|
if (depth < elt.depth) { |
|
depth = elt.depth; |
|
} |
|
if (height < elt.height) { |
|
height = elt.height; |
|
} |
|
outrow[c] = elt; |
|
} |
|
|
|
var gap = 0; |
|
if (group.value.rowGaps[r]) { |
|
gap = group.value.rowGaps[r].value; |
|
switch (gap.unit) { |
|
case "em": |
|
gap = gap.number; |
|
break; |
|
case "ex": |
|
gap = gap.number * fontMetrics.metrics.emPerEx; |
|
break; |
|
default: |
|
console.error("Can't handle unit " + gap.unit); |
|
gap = 0; |
|
} |
|
if (gap > 0) { |
|
gap += arstrutDepth; |
|
if (depth < gap) { |
|
depth = gap; |
|
} |
|
gap = 0; |
|
} |
|
} |
|
|
|
outrow.height = height; |
|
outrow.depth = depth; |
|
totalHeight += height; |
|
outrow.pos = totalHeight; |
|
totalHeight += depth + gap; |
|
body[r] = outrow; |
|
} |
|
|
|
var offset = totalHeight / 2 + fontMetrics.metrics.axisHeight; |
|
var colDescriptions = group.value.cols || []; |
|
var cols = []; |
|
var colSep; |
|
var colDescrNum; |
|
for (c = 0, colDescrNum = 0; |
|
|
|
|
|
c < nc || colDescrNum < colDescriptions.length; |
|
++c, ++colDescrNum) { |
|
|
|
var colDescr = colDescriptions[colDescrNum] || {}; |
|
|
|
var firstSeparator = true; |
|
while (colDescr.type === "separator") { |
|
|
|
|
|
if (!firstSeparator) { |
|
colSep = makeSpan(["arraycolsep"], []); |
|
colSep.style.width = |
|
fontMetrics.metrics.doubleRuleSep + "em"; |
|
cols.push(colSep); |
|
} |
|
|
|
if (colDescr.separator === "|") { |
|
var separator = makeSpan( |
|
["vertical-separator"], |
|
[]); |
|
separator.style.height = totalHeight + "em"; |
|
separator.style.verticalAlign = |
|
-(totalHeight - offset) + "em"; |
|
|
|
cols.push(separator); |
|
} else { |
|
throw new ParseError( |
|
"Invalid separator type: " + colDescr.separator); |
|
} |
|
|
|
colDescrNum++; |
|
colDescr = colDescriptions[colDescrNum] || {}; |
|
firstSeparator = false; |
|
} |
|
|
|
if (c >= nc) { |
|
continue; |
|
} |
|
|
|
var sepwidth; |
|
if (c > 0 || group.value.hskipBeforeAndAfter) { |
|
sepwidth = utils.deflt(colDescr.pregap, arraycolsep); |
|
if (sepwidth !== 0) { |
|
colSep = makeSpan(["arraycolsep"], []); |
|
colSep.style.width = sepwidth + "em"; |
|
cols.push(colSep); |
|
} |
|
} |
|
|
|
var col = []; |
|
for (r = 0; r < nr; ++r) { |
|
var row = body[r]; |
|
var elem = row[c]; |
|
if (!elem) { |
|
continue; |
|
} |
|
var shift = row.pos - offset; |
|
elem.depth = row.depth; |
|
elem.height = row.height; |
|
col.push({type: "elem", elem: elem, shift: shift}); |
|
} |
|
|
|
col = buildCommon.makeVList(col, "individualShift", null, options); |
|
col = makeSpan( |
|
["col-align-" + (colDescr.align || "c")], |
|
[col]); |
|
cols.push(col); |
|
|
|
if (c < nc - 1 || group.value.hskipBeforeAndAfter) { |
|
sepwidth = utils.deflt(colDescr.postgap, arraycolsep); |
|
if (sepwidth !== 0) { |
|
colSep = makeSpan(["arraycolsep"], []); |
|
colSep.style.width = sepwidth + "em"; |
|
cols.push(colSep); |
|
} |
|
} |
|
} |
|
body = makeSpan(["mtable"], cols); |
|
return makeSpan(["mord"], [body], options.getColor()); |
|
}; |
|
|
|
groupTypes.spacing = function(group, options, prev) { |
|
if (group.value === "\\ " || group.value === "\\space" || |
|
group.value === " " || group.value === "~") { |
|
|
|
|
|
|
|
return makeSpan( |
|
["mord", "mspace"], |
|
[buildCommon.mathsym(group.value, group.mode)] |
|
); |
|
} else { |
|
|
|
|
|
return makeSpan( |
|
["mord", "mspace", |
|
buildCommon.spacingFunctions[group.value].className]); |
|
} |
|
}; |
|
|
|
groupTypes.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]); |
|
}; |
|
|
|
groupTypes.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]); |
|
}; |
|
|
|
groupTypes.op = function(group, options, prev) { |
|
|
|
var supGroup; |
|
var subGroup; |
|
var hasLimits = false; |
|
if (group.type === "supsub" ) { |
|
|
|
|
|
|
|
supGroup = group.value.sup; |
|
subGroup = group.value.sub; |
|
group = group.value.base; |
|
hasLimits = true; |
|
} |
|
|
|
|
|
var noSuccessor = [ |
|
"\\smallint", |
|
]; |
|
|
|
var large = false; |
|
if (options.style.size === Style.DISPLAY.size && |
|
group.value.symbol && |
|
!utils.contains(noSuccessor, group.value.body)) { |
|
|
|
|
|
large = true; |
|
} |
|
|
|
var base; |
|
var baseShift = 0; |
|
var slant = 0; |
|
if (group.value.symbol) { |
|
|
|
var style = large ? "Size2-Regular" : "Size1-Regular"; |
|
base = buildCommon.makeSymbol( |
|
group.value.body, style, "math", options.getColor(), |
|
["op-symbol", large ? "large-op" : "small-op", "mop"]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
baseShift = (base.height - base.depth) / 2 - |
|
fontMetrics.metrics.axisHeight * |
|
options.style.sizeMultiplier; |
|
|
|
|
|
slant = base.italic; |
|
} else { |
|
|
|
|
|
|
|
|
|
var output = []; |
|
for (var i = 1; i < group.value.body.length; i++) { |
|
output.push(buildCommon.mathsym(group.value.body[i], group.mode)); |
|
} |
|
base = makeSpan(["mop"], output, options.getColor()); |
|
} |
|
|
|
if (hasLimits) { |
|
|
|
|
|
base = makeSpan([], [base]); |
|
|
|
var supmid; |
|
var supKern; |
|
var submid; |
|
var subKern; |
|
|
|
|
|
if (supGroup) { |
|
var sup = buildGroup( |
|
supGroup, options.withStyle(options.style.sup())); |
|
supmid = makeSpan( |
|
[options.style.reset(), options.style.sup().cls()], [sup]); |
|
|
|
supKern = Math.max( |
|
fontMetrics.metrics.bigOpSpacing1, |
|
fontMetrics.metrics.bigOpSpacing3 - sup.depth); |
|
} |
|
|
|
if (subGroup) { |
|
var sub = buildGroup( |
|
subGroup, options.withStyle(options.style.sub())); |
|
submid = makeSpan( |
|
[options.style.reset(), options.style.sub().cls()], |
|
[sub]); |
|
|
|
subKern = Math.max( |
|
fontMetrics.metrics.bigOpSpacing2, |
|
fontMetrics.metrics.bigOpSpacing4 - sub.height); |
|
} |
|
|
|
|
|
|
|
var finalGroup; |
|
var top; |
|
var bottom; |
|
if (!supGroup) { |
|
top = base.height - baseShift; |
|
|
|
finalGroup = buildCommon.makeVList([ |
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, |
|
{type: "elem", elem: submid}, |
|
{type: "kern", size: subKern}, |
|
{type: "elem", elem: base}, |
|
], "top", top, options); |
|
|
|
|
|
|
|
|
|
|
|
finalGroup.children[0].style.marginLeft = -slant + "em"; |
|
} else if (!subGroup) { |
|
bottom = base.depth + baseShift; |
|
|
|
finalGroup = buildCommon.makeVList([ |
|
{type: "elem", elem: base}, |
|
{type: "kern", size: supKern}, |
|
{type: "elem", elem: supmid}, |
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, |
|
], "bottom", bottom, options); |
|
|
|
|
|
finalGroup.children[1].style.marginLeft = slant + "em"; |
|
} else if (!supGroup && !subGroup) { |
|
|
|
|
|
|
|
return base; |
|
} else { |
|
bottom = fontMetrics.metrics.bigOpSpacing5 + |
|
submid.height + submid.depth + |
|
subKern + |
|
base.depth + baseShift; |
|
|
|
finalGroup = buildCommon.makeVList([ |
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, |
|
{type: "elem", elem: submid}, |
|
{type: "kern", size: subKern}, |
|
{type: "elem", elem: base}, |
|
{type: "kern", size: supKern}, |
|
{type: "elem", elem: supmid}, |
|
{type: "kern", size: fontMetrics.metrics.bigOpSpacing5}, |
|
], "bottom", bottom, options); |
|
|
|
|
|
finalGroup.children[0].style.marginLeft = -slant + "em"; |
|
finalGroup.children[2].style.marginLeft = slant + "em"; |
|
} |
|
|
|
return makeSpan(["mop", "op-limits"], [finalGroup]); |
|
} else { |
|
if (group.value.symbol) { |
|
base.style.top = baseShift + "em"; |
|
} |
|
|
|
return base; |
|
} |
|
}; |
|
|
|
groupTypes.katex = function(group, options, prev) { |
|
|
|
|
|
|
|
var k = makeSpan( |
|
["k"], [buildCommon.mathsym("K", group.mode)]); |
|
var a = makeSpan( |
|
["a"], [buildCommon.mathsym("A", group.mode)]); |
|
|
|
a.height = (a.height + 0.2) * 0.75; |
|
a.depth = (a.height - 0.2) * 0.75; |
|
|
|
var t = makeSpan( |
|
["t"], [buildCommon.mathsym("T", group.mode)]); |
|
var e = makeSpan( |
|
["e"], [buildCommon.mathsym("E", group.mode)]); |
|
|
|
e.height = (e.height - 0.2155); |
|
e.depth = (e.depth + 0.2155); |
|
|
|
var x = makeSpan( |
|
["x"], [buildCommon.mathsym("X", group.mode)]); |
|
|
|
return makeSpan( |
|
["katex-logo", "mord"], [k, a, t, e, x], options.getColor()); |
|
}; |
|
|
|
groupTypes.overline = function(group, options, prev) { |
|
|
|
|
|
|
|
var innerGroup = buildGroup(group.value.body, |
|
options.withStyle(options.style.cramp())); |
|
|
|
var ruleWidth = fontMetrics.metrics.defaultRuleThickness / |
|
options.style.sizeMultiplier; |
|
|
|
|
|
var line = makeSpan( |
|
[options.style.reset(), Style.TEXT.cls(), "overline-line"]); |
|
line.height = ruleWidth; |
|
line.maxFontSize = 1.0; |
|
|
|
|
|
var vlist = buildCommon.makeVList([ |
|
{type: "elem", elem: innerGroup}, |
|
{type: "kern", size: 3 * ruleWidth}, |
|
{type: "elem", elem: line}, |
|
{type: "kern", size: ruleWidth}, |
|
], "firstBaseline", null, options); |
|
|
|
return makeSpan(["overline", "mord"], [vlist], options.getColor()); |
|
}; |
|
|
|
groupTypes.underline = function(group, options, prev) { |
|
|
|
|
|
|
|
var innerGroup = buildGroup(group.value.body, options); |
|
|
|
var ruleWidth = fontMetrics.metrics.defaultRuleThickness / |
|
options.style.sizeMultiplier; |
|
|
|
|
|
var line = makeSpan( |
|
[options.style.reset(), Style.TEXT.cls(), "underline-line"]); |
|
line.height = ruleWidth; |
|
line.maxFontSize = 1.0; |
|
|
|
|
|
var vlist = buildCommon.makeVList([ |
|
{type: "kern", size: ruleWidth}, |
|
{type: "elem", elem: line}, |
|
{type: "kern", size: 3 * ruleWidth}, |
|
{type: "elem", elem: innerGroup}, |
|
], "top", innerGroup.height, options); |
|
|
|
return makeSpan(["underline", "mord"], [vlist], options.getColor()); |
|
}; |
|
|
|
groupTypes.sqrt = function(group, options, prev) { |
|
|
|
|
|
|
|
|
|
var inner = buildGroup(group.value.body, |
|
options.withStyle(options.style.cramp())); |
|
|
|
var ruleWidth = fontMetrics.metrics.defaultRuleThickness / |
|
options.style.sizeMultiplier; |
|
|
|
var line = makeSpan( |
|
[options.style.reset(), Style.TEXT.cls(), "sqrt-line"], [], |
|
options.getColor()); |
|
line.height = ruleWidth; |
|
line.maxFontSize = 1.0; |
|
|
|
var phi = ruleWidth; |
|
if (options.style.id < Style.TEXT.id) { |
|
phi = fontMetrics.metrics.xHeight; |
|
} |
|
|
|
|
|
var lineClearance = ruleWidth + phi / 4; |
|
|
|
var innerHeight = |
|
(inner.height + inner.depth) * options.style.sizeMultiplier; |
|
var minDelimiterHeight = innerHeight + lineClearance + ruleWidth; |
|
|
|
|
|
var delim = makeSpan(["sqrt-sign"], [ |
|
delimiter.customSizedDelim("\\surd", minDelimiterHeight, |
|
false, options, group.mode)], |
|
options.getColor()); |
|
|
|
var delimDepth = (delim.height + delim.depth) - ruleWidth; |
|
|
|
|
|
if (delimDepth > inner.height + inner.depth + lineClearance) { |
|
lineClearance = |
|
(lineClearance + delimDepth - inner.height - inner.depth) / 2; |
|
} |
|
|
|
|
|
var delimShift = -(inner.height + lineClearance + ruleWidth) + delim.height; |
|
delim.style.top = delimShift + "em"; |
|
delim.height -= delimShift; |
|
delim.depth += delimShift; |
|
|
|
|
|
|
|
|
|
|
|
|
|
var body; |
|
if (inner.height === 0 && inner.depth === 0) { |
|
body = makeSpan(); |
|
} else { |
|
body = buildCommon.makeVList([ |
|
{type: "elem", elem: inner}, |
|
{type: "kern", size: lineClearance}, |
|
{type: "elem", elem: line}, |
|
{type: "kern", size: ruleWidth}, |
|
], "firstBaseline", null, options); |
|
} |
|
|
|
if (!group.value.index) { |
|
return makeSpan(["sqrt", "mord"], [delim, body]); |
|
} else { |
|
|
|
|
|
|
|
var root = buildGroup( |
|
group.value.index, |
|
options.withStyle(Style.SCRIPTSCRIPT)); |
|
var rootWrap = makeSpan( |
|
[options.style.reset(), Style.SCRIPTSCRIPT.cls()], |
|
[root]); |
|
|
|
|
|
var innerRootHeight = Math.max(delim.height, body.height); |
|
var innerRootDepth = Math.max(delim.depth, body.depth); |
|
|
|
|
|
|
|
var toShift = 0.6 * (innerRootHeight - innerRootDepth); |
|
|
|
|
|
var rootVList = buildCommon.makeVList( |
|
[{type: "elem", elem: rootWrap}], |
|
"shift", -toShift, options); |
|
|
|
|
|
var rootVListWrap = makeSpan(["root"], [rootVList]); |
|
|
|
return makeSpan(["sqrt", "mord"], [rootVListWrap, delim, body]); |
|
} |
|
}; |
|
|
|
groupTypes.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 fontSize = buildCommon.sizingMultiplier[group.value.size]; |
|
span.maxFontSize = fontSize * options.style.sizeMultiplier; |
|
|
|
return span; |
|
}; |
|
|
|
groupTypes.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); |
|
}; |
|
|
|
groupTypes.font = function(group, options, prev) { |
|
var font = group.value.font; |
|
return buildGroup(group.value.body, options.withFont(font), prev); |
|
}; |
|
|
|
groupTypes.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)]); |
|
}; |
|
|
|
groupTypes.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 = makeNullDelimiter(options); |
|
} else { |
|
|
|
|
|
leftDelim = delimiter.leftRightDelim( |
|
group.value.left, innerHeight, innerDepth, options, |
|
group.mode); |
|
} |
|
|
|
inner.unshift(leftDelim); |
|
|
|
var rightDelim; |
|
|
|
if (group.value.right === ".") { |
|
rightDelim = makeNullDelimiter(options); |
|
} else { |
|
rightDelim = delimiter.leftRightDelim( |
|
group.value.right, innerHeight, innerDepth, options, |
|
group.mode); |
|
} |
|
|
|
inner.push(rightDelim); |
|
|
|
return makeSpan( |
|
["minner", options.style.cls()], inner, options.getColor()); |
|
}; |
|
|
|
groupTypes.rule = function(group, options, prev) { |
|
|
|
var rule = makeSpan(["mord", "rule"], [], options.getColor()); |
|
|
|
|
|
var shift = 0; |
|
if (group.value.shift) { |
|
shift = group.value.shift.number; |
|
if (group.value.shift.unit === "ex") { |
|
shift *= fontMetrics.metrics.xHeight; |
|
} |
|
} |
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
shift /= options.style.sizeMultiplier; |
|
width /= options.style.sizeMultiplier; |
|
height /= options.style.sizeMultiplier; |
|
|
|
|
|
rule.style.borderRightWidth = width + "em"; |
|
rule.style.borderTopWidth = height + "em"; |
|
rule.style.bottom = shift + "em"; |
|
|
|
|
|
rule.width = width; |
|
rule.height = height + shift; |
|
rule.depth = -shift; |
|
|
|
return rule; |
|
}; |
|
|
|
groupTypes.accent = function(group, options, prev) { |
|
|
|
var base = group.value.base; |
|
|
|
var supsubGroup; |
|
if (group.type === "supsub") { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var supsub = group; |
|
|
|
group = supsub.value.base; |
|
|
|
base = group.value.base; |
|
|
|
supsub.value.base = base; |
|
|
|
|
|
|
|
supsubGroup = buildGroup( |
|
supsub, options.reset(), prev); |
|
} |
|
|
|
|
|
var body = buildGroup( |
|
base, options.withStyle(options.style.cramp())); |
|
|
|
|
|
|
|
|
|
|
|
|
|
var skew; |
|
if (isCharacterBox(base)) { |
|
|
|
|
|
var baseChar = getBaseElem(base); |
|
|
|
var baseGroup = buildGroup( |
|
baseChar, options.withStyle(options.style.cramp())); |
|
|
|
skew = baseGroup.skew; |
|
|
|
|
|
|
|
|
|
} else { |
|
skew = 0; |
|
} |
|
|
|
|
|
var clearance = Math.min(body.height, fontMetrics.metrics.xHeight); |
|
|
|
|
|
var accent = buildCommon.makeSymbol( |
|
group.value.accent, "Main-Regular", "math", options.getColor()); |
|
|
|
|
|
accent.italic = 0; |
|
|
|
|
|
|
|
|
|
|
|
var vecClass = group.value.accent === "\\vec" ? "accent-vec" : null; |
|
|
|
var accentBody = makeSpan(["accent-body", vecClass], [ |
|
makeSpan([], [accent])]); |
|
|
|
accentBody = buildCommon.makeVList([ |
|
{type: "elem", elem: body}, |
|
{type: "kern", size: -clearance}, |
|
{type: "elem", elem: accentBody}, |
|
], "firstBaseline", null, options); |
|
|
|
|
|
|
|
|
|
accentBody.children[1].style.marginLeft = 2 * skew + "em"; |
|
|
|
var accentWrap = makeSpan(["mord", "accent"], [accentBody]); |
|
|
|
if (supsubGroup) { |
|
|
|
|
|
supsubGroup.children[0] = accentWrap; |
|
|
|
|
|
|
|
supsubGroup.height = Math.max(accentWrap.height, supsubGroup.height); |
|
|
|
|
|
supsubGroup.classes[0] = "mord"; |
|
|
|
return supsubGroup; |
|
} else { |
|
return accentWrap; |
|
} |
|
}; |
|
|
|
groupTypes.phantom = function(group, options, prev) { |
|
var elements = buildExpression( |
|
group.value.value, |
|
options.withPhantom(), |
|
prev |
|
); |
|
|
|
|
|
|
|
return new buildCommon.makeFragment(elements); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
var buildGroup = function(group, options, prev) { |
|
if (!group) { |
|
return makeSpan(); |
|
} |
|
|
|
if (groupTypes[group.type]) { |
|
|
|
var groupNode = groupTypes[group.type](group, options, prev); |
|
var multiplier; |
|
|
|
|
|
|
|
if (options.style !== options.parentStyle) { |
|
multiplier = options.style.sizeMultiplier / |
|
options.parentStyle.sizeMultiplier; |
|
|
|
groupNode.height *= multiplier; |
|
groupNode.depth *= multiplier; |
|
} |
|
|
|
|
|
|
|
if (options.size !== options.parentSize) { |
|
multiplier = buildCommon.sizingMultiplier[options.size] / |
|
buildCommon.sizingMultiplier[options.parentSize]; |
|
|
|
groupNode.height *= multiplier; |
|
groupNode.depth *= multiplier; |
|
} |
|
|
|
return groupNode; |
|
} else { |
|
throw new ParseError( |
|
"Got group of unknown type: '" + group.type + "'"); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
var buildHTML = function(tree, options) { |
|
|
|
|
|
tree = JSON.parse(JSON.stringify(tree)); |
|
|
|
|
|
var expression = buildExpression(tree, options); |
|
var body = makeSpan(["base", options.style.cls()], expression); |
|
|
|
|
|
|
|
|
|
var topStrut = makeSpan(["strut"]); |
|
var bottomStrut = makeSpan(["strut", "bottom"]); |
|
|
|
topStrut.style.height = body.height + "em"; |
|
bottomStrut.style.height = (body.height + body.depth) + "em"; |
|
|
|
|
|
|
|
bottomStrut.style.verticalAlign = -body.depth + "em"; |
|
|
|
|
|
var htmlNode = makeSpan(["katex-html"], [topStrut, bottomStrut, body]); |
|
|
|
htmlNode.setAttribute("aria-hidden", "true"); |
|
|
|
return htmlNode; |
|
}; |
|
|
|
module.exports = buildHTML; |
|
|