|
|
|
import defineFunction, {normalizeArgument} from "../defineFunction"; |
|
import buildCommon from "../buildCommon"; |
|
import delimiter from "../delimiter"; |
|
import mathMLTree from "../mathMLTree"; |
|
import Style from "../Style"; |
|
import {assertNodeType} from "../parseNode"; |
|
import {assert} from "../utils"; |
|
|
|
import * as html from "../buildHTML"; |
|
import * as mml from "../buildMathML"; |
|
import {calculateSize, makeEm} from "../units"; |
|
|
|
const adjustStyle = (size, originalStyle) => { |
|
|
|
|
|
let style = originalStyle; |
|
if (size === "display") { |
|
|
|
|
|
style = style.id >= Style.SCRIPT.id ? style.text() : Style.DISPLAY; |
|
} else if (size === "text" && |
|
style.size === Style.DISPLAY.size) { |
|
|
|
style = Style.TEXT; |
|
} else if (size === "script") { |
|
style = Style.SCRIPT; |
|
} else if (size === "scriptscript") { |
|
style = Style.SCRIPTSCRIPT; |
|
} |
|
return style; |
|
}; |
|
|
|
const htmlBuilder = (group, options) => { |
|
|
|
const style = adjustStyle(group.size, options.style); |
|
|
|
const nstyle = style.fracNum(); |
|
const dstyle = style.fracDen(); |
|
let newOptions; |
|
|
|
newOptions = options.havingStyle(nstyle); |
|
const numerm = html.buildGroup(group.numer, newOptions, options); |
|
|
|
if (group.continued) { |
|
|
|
|
|
const hStrut = 8.5 / options.fontMetrics().ptPerEm; |
|
const dStrut = 3.5 / options.fontMetrics().ptPerEm; |
|
numerm.height = numerm.height < hStrut ? hStrut : numerm.height; |
|
numerm.depth = numerm.depth < dStrut ? dStrut : numerm.depth; |
|
} |
|
|
|
newOptions = options.havingStyle(dstyle); |
|
const denomm = html.buildGroup(group.denom, newOptions, options); |
|
|
|
let rule; |
|
let ruleWidth; |
|
let ruleSpacing; |
|
if (group.hasBarLine) { |
|
if (group.barSize) { |
|
ruleWidth = calculateSize(group.barSize, options); |
|
rule = buildCommon.makeLineSpan("frac-line", options, ruleWidth); |
|
} else { |
|
rule = buildCommon.makeLineSpan("frac-line", options); |
|
} |
|
ruleWidth = rule.height; |
|
ruleSpacing = rule.height; |
|
} else { |
|
rule = null; |
|
ruleWidth = 0; |
|
ruleSpacing = options.fontMetrics().defaultRuleThickness; |
|
} |
|
|
|
|
|
let numShift; |
|
let clearance; |
|
let denomShift; |
|
if (style.size === Style.DISPLAY.size || group.size === "display") { |
|
numShift = options.fontMetrics().num1; |
|
if (ruleWidth > 0) { |
|
clearance = 3 * ruleSpacing; |
|
} else { |
|
clearance = 7 * ruleSpacing; |
|
} |
|
denomShift = options.fontMetrics().denom1; |
|
} else { |
|
if (ruleWidth > 0) { |
|
numShift = options.fontMetrics().num2; |
|
clearance = ruleSpacing; |
|
} else { |
|
numShift = options.fontMetrics().num3; |
|
clearance = 3 * ruleSpacing; |
|
} |
|
denomShift = options.fontMetrics().denom2; |
|
} |
|
|
|
let frac; |
|
if (!rule) { |
|
|
|
const candidateClearance = |
|
(numShift - numerm.depth) - (denomm.height - denomShift); |
|
if (candidateClearance < clearance) { |
|
numShift += 0.5 * (clearance - candidateClearance); |
|
denomShift += 0.5 * (clearance - candidateClearance); |
|
} |
|
|
|
frac = buildCommon.makeVList({ |
|
positionType: "individualShift", |
|
children: [ |
|
{type: "elem", elem: denomm, shift: denomShift}, |
|
{type: "elem", elem: numerm, shift: -numShift}, |
|
], |
|
}, options); |
|
} else { |
|
|
|
const axisHeight = options.fontMetrics().axisHeight; |
|
|
|
if ((numShift - numerm.depth) - (axisHeight + 0.5 * ruleWidth) < |
|
clearance) { |
|
numShift += |
|
clearance - ((numShift - numerm.depth) - |
|
(axisHeight + 0.5 * ruleWidth)); |
|
} |
|
|
|
if ((axisHeight - 0.5 * ruleWidth) - (denomm.height - denomShift) < |
|
clearance) { |
|
denomShift += |
|
clearance - ((axisHeight - 0.5 * ruleWidth) - |
|
(denomm.height - denomShift)); |
|
} |
|
|
|
const midShift = -(axisHeight - 0.5 * ruleWidth); |
|
|
|
frac = buildCommon.makeVList({ |
|
positionType: "individualShift", |
|
children: [ |
|
{type: "elem", elem: denomm, shift: denomShift}, |
|
{type: "elem", elem: rule, shift: midShift}, |
|
{type: "elem", elem: numerm, shift: -numShift}, |
|
], |
|
}, options); |
|
} |
|
|
|
|
|
|
|
newOptions = options.havingStyle(style); |
|
frac.height *= newOptions.sizeMultiplier / options.sizeMultiplier; |
|
frac.depth *= newOptions.sizeMultiplier / options.sizeMultiplier; |
|
|
|
|
|
let delimSize; |
|
if (style.size === Style.DISPLAY.size) { |
|
delimSize = options.fontMetrics().delim1; |
|
} else if (style.size === Style.SCRIPTSCRIPT.size) { |
|
delimSize = options.havingStyle(Style.SCRIPT).fontMetrics().delim2; |
|
} else { |
|
delimSize = options.fontMetrics().delim2; |
|
} |
|
|
|
let leftDelim; |
|
let rightDelim; |
|
if (group.leftDelim == null) { |
|
leftDelim = html.makeNullDelimiter(options, ["mopen"]); |
|
} else { |
|
leftDelim = delimiter.customSizedDelim( |
|
group.leftDelim, delimSize, true, |
|
options.havingStyle(style), group.mode, ["mopen"]); |
|
} |
|
|
|
if (group.continued) { |
|
rightDelim = buildCommon.makeSpan([]); |
|
} else if (group.rightDelim == null) { |
|
rightDelim = html.makeNullDelimiter(options, ["mclose"]); |
|
} else { |
|
rightDelim = delimiter.customSizedDelim( |
|
group.rightDelim, delimSize, true, |
|
options.havingStyle(style), group.mode, ["mclose"]); |
|
} |
|
|
|
return buildCommon.makeSpan( |
|
["mord"].concat(newOptions.sizingClasses(options)), |
|
[leftDelim, buildCommon.makeSpan(["mfrac"], [frac]), rightDelim], |
|
options); |
|
}; |
|
|
|
const mathmlBuilder = (group, options) => { |
|
let node = new mathMLTree.MathNode( |
|
"mfrac", |
|
[ |
|
mml.buildGroup(group.numer, options), |
|
mml.buildGroup(group.denom, options), |
|
]); |
|
|
|
if (!group.hasBarLine) { |
|
node.setAttribute("linethickness", "0px"); |
|
} else if (group.barSize) { |
|
const ruleWidth = calculateSize(group.barSize, options); |
|
node.setAttribute("linethickness", makeEm(ruleWidth)); |
|
} |
|
|
|
const style = adjustStyle(group.size, options.style); |
|
if (style.size !== options.style.size) { |
|
node = new mathMLTree.MathNode("mstyle", [node]); |
|
const isDisplay = (style.size === Style.DISPLAY.size) ? "true" : "false"; |
|
node.setAttribute("displaystyle", isDisplay); |
|
node.setAttribute("scriptlevel", "0"); |
|
} |
|
|
|
if (group.leftDelim != null || group.rightDelim != null) { |
|
const withDelims = []; |
|
|
|
if (group.leftDelim != null) { |
|
const leftOp = new mathMLTree.MathNode( |
|
"mo", |
|
[new mathMLTree.TextNode(group.leftDelim.replace("\\", ""))] |
|
); |
|
|
|
leftOp.setAttribute("fence", "true"); |
|
|
|
withDelims.push(leftOp); |
|
} |
|
|
|
withDelims.push(node); |
|
|
|
if (group.rightDelim != null) { |
|
const rightOp = new mathMLTree.MathNode( |
|
"mo", |
|
[new mathMLTree.TextNode(group.rightDelim.replace("\\", ""))] |
|
); |
|
|
|
rightOp.setAttribute("fence", "true"); |
|
|
|
withDelims.push(rightOp); |
|
} |
|
|
|
return mml.makeRow(withDelims); |
|
} |
|
|
|
return node; |
|
}; |
|
|
|
defineFunction({ |
|
type: "genfrac", |
|
names: [ |
|
"\\dfrac", "\\frac", "\\tfrac", |
|
"\\dbinom", "\\binom", "\\tbinom", |
|
"\\\\atopfrac", |
|
"\\\\bracefrac", "\\\\brackfrac", |
|
], |
|
props: { |
|
numArgs: 2, |
|
allowedInArgument: true, |
|
}, |
|
handler: ({parser, funcName}, args) => { |
|
const numer = args[0]; |
|
const denom = args[1]; |
|
let hasBarLine; |
|
let leftDelim = null; |
|
let rightDelim = null; |
|
let size = "auto"; |
|
|
|
switch (funcName) { |
|
case "\\dfrac": |
|
case "\\frac": |
|
case "\\tfrac": |
|
hasBarLine = true; |
|
break; |
|
case "\\\\atopfrac": |
|
hasBarLine = false; |
|
break; |
|
case "\\dbinom": |
|
case "\\binom": |
|
case "\\tbinom": |
|
hasBarLine = false; |
|
leftDelim = "("; |
|
rightDelim = ")"; |
|
break; |
|
case "\\\\bracefrac": |
|
hasBarLine = false; |
|
leftDelim = "\\{"; |
|
rightDelim = "\\}"; |
|
break; |
|
case "\\\\brackfrac": |
|
hasBarLine = false; |
|
leftDelim = "["; |
|
rightDelim = "]"; |
|
break; |
|
default: |
|
throw new Error("Unrecognized genfrac command"); |
|
} |
|
|
|
switch (funcName) { |
|
case "\\dfrac": |
|
case "\\dbinom": |
|
size = "display"; |
|
break; |
|
case "\\tfrac": |
|
case "\\tbinom": |
|
size = "text"; |
|
break; |
|
} |
|
|
|
return { |
|
type: "genfrac", |
|
mode: parser.mode, |
|
continued: false, |
|
numer, |
|
denom, |
|
hasBarLine, |
|
leftDelim, |
|
rightDelim, |
|
size, |
|
barSize: null, |
|
}; |
|
}, |
|
|
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
defineFunction({ |
|
type: "genfrac", |
|
names: ["\\cfrac"], |
|
props: { |
|
numArgs: 2, |
|
}, |
|
handler: ({parser, funcName}, args) => { |
|
const numer = args[0]; |
|
const denom = args[1]; |
|
|
|
return { |
|
type: "genfrac", |
|
mode: parser.mode, |
|
continued: true, |
|
numer, |
|
denom, |
|
hasBarLine: true, |
|
leftDelim: null, |
|
rightDelim: null, |
|
size: "display", |
|
barSize: null, |
|
}; |
|
}, |
|
}); |
|
|
|
|
|
|
|
defineFunction({ |
|
type: "infix", |
|
names: ["\\over", "\\choose", "\\atop", "\\brace", "\\brack"], |
|
props: { |
|
numArgs: 0, |
|
infix: true, |
|
}, |
|
handler({parser, funcName, token}) { |
|
let replaceWith; |
|
switch (funcName) { |
|
case "\\over": |
|
replaceWith = "\\frac"; |
|
break; |
|
case "\\choose": |
|
replaceWith = "\\binom"; |
|
break; |
|
case "\\atop": |
|
replaceWith = "\\\\atopfrac"; |
|
break; |
|
case "\\brace": |
|
replaceWith = "\\\\bracefrac"; |
|
break; |
|
case "\\brack": |
|
replaceWith = "\\\\brackfrac"; |
|
break; |
|
default: |
|
throw new Error("Unrecognized infix genfrac command"); |
|
} |
|
return { |
|
type: "infix", |
|
mode: parser.mode, |
|
replaceWith, |
|
token, |
|
}; |
|
}, |
|
}); |
|
|
|
const stylArray = ["display", "text", "script", "scriptscript"]; |
|
|
|
const delimFromValue = function(delimString: string): string | null { |
|
let delim = null; |
|
if (delimString.length > 0) { |
|
delim = delimString; |
|
delim = delim === "." ? null : delim; |
|
} |
|
return delim; |
|
}; |
|
|
|
defineFunction({ |
|
type: "genfrac", |
|
names: ["\\genfrac"], |
|
props: { |
|
numArgs: 6, |
|
allowedInArgument: true, |
|
argTypes: ["math", "math", "size", "text", "math", "math"], |
|
}, |
|
handler({parser}, args) { |
|
const numer = args[4]; |
|
const denom = args[5]; |
|
|
|
|
|
const leftNode = normalizeArgument(args[0]); |
|
const leftDelim = leftNode.type === "atom" && leftNode.family === "open" |
|
? delimFromValue(leftNode.text) : null; |
|
const rightNode = normalizeArgument(args[1]); |
|
const rightDelim = rightNode.type === "atom" && rightNode.family === "close" |
|
? delimFromValue(rightNode.text) : null; |
|
|
|
const barNode = assertNodeType(args[2], "size"); |
|
let hasBarLine; |
|
let barSize = null; |
|
if (barNode.isBlank) { |
|
|
|
|
|
|
|
hasBarLine = true; |
|
} else { |
|
barSize = barNode.value; |
|
hasBarLine = barSize.number > 0; |
|
} |
|
|
|
|
|
let size = "auto"; |
|
let styl = args[3]; |
|
if (styl.type === "ordgroup") { |
|
if (styl.body.length > 0) { |
|
const textOrd = assertNodeType(styl.body[0], "textord"); |
|
size = stylArray[Number(textOrd.text)]; |
|
} |
|
} else { |
|
styl = assertNodeType(styl, "textord"); |
|
size = stylArray[Number(styl.text)]; |
|
} |
|
|
|
return { |
|
type: "genfrac", |
|
mode: parser.mode, |
|
numer, |
|
denom, |
|
continued: false, |
|
hasBarLine, |
|
barSize, |
|
leftDelim, |
|
rightDelim, |
|
size, |
|
}; |
|
}, |
|
|
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
|
|
defineFunction({ |
|
type: "infix", |
|
names: ["\\above"], |
|
props: { |
|
numArgs: 1, |
|
argTypes: ["size"], |
|
infix: true, |
|
}, |
|
handler({parser, funcName, token}, args) { |
|
return { |
|
type: "infix", |
|
mode: parser.mode, |
|
replaceWith: "\\\\abovefrac", |
|
size: assertNodeType(args[0], "size").value, |
|
token, |
|
}; |
|
}, |
|
}); |
|
|
|
defineFunction({ |
|
type: "genfrac", |
|
names: ["\\\\abovefrac"], |
|
props: { |
|
numArgs: 3, |
|
argTypes: ["math", "size", "math"], |
|
}, |
|
handler: ({parser, funcName}, args) => { |
|
const numer = args[0]; |
|
const barSize = assert(assertNodeType(args[1], "infix").size); |
|
const denom = args[2]; |
|
|
|
const hasBarLine = barSize.number > 0; |
|
return { |
|
type: "genfrac", |
|
mode: parser.mode, |
|
numer, |
|
denom, |
|
continued: false, |
|
hasBarLine, |
|
barSize, |
|
leftDelim: null, |
|
rightDelim: null, |
|
size: "auto", |
|
}; |
|
}, |
|
|
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|