|
|
|
import defineFunction from "../defineFunction"; |
|
import buildCommon from "../buildCommon"; |
|
import mathMLTree from "../mathMLTree"; |
|
import utils from "../utils"; |
|
import stretchy from "../stretchy"; |
|
import {phasePath} from "../svgGeometry"; |
|
import {PathNode, SvgNode} from "../domTree"; |
|
import {calculateSize, makeEm} from "../units"; |
|
import {assertNodeType} from "../parseNode"; |
|
|
|
import * as html from "../buildHTML"; |
|
import * as mml from "../buildMathML"; |
|
|
|
|
|
const htmlBuilder = (group, options) => { |
|
|
|
|
|
|
|
const inner = buildCommon.wrapFragment( |
|
html.buildGroup(group.body, options), options); |
|
|
|
const label = group.label.slice(1); |
|
let scale = options.sizeMultiplier; |
|
let img; |
|
let imgShift = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
const isSingleChar = utils.isCharacterBox(group.body); |
|
|
|
if (label === "sout") { |
|
img = buildCommon.makeSpan(["stretchy", "sout"]); |
|
img.height = options.fontMetrics().defaultRuleThickness / scale; |
|
imgShift = -0.5 * options.fontMetrics().xHeight; |
|
|
|
} else if (label === "phase") { |
|
|
|
const lineWeight = calculateSize({number: 0.6, unit: "pt"}, options); |
|
const clearance = calculateSize({number: 0.35, unit: "ex"}, options); |
|
|
|
|
|
const newOptions = options.havingBaseSizing(); |
|
scale = scale / newOptions.sizeMultiplier; |
|
|
|
const angleHeight = inner.height + inner.depth + lineWeight + clearance; |
|
|
|
inner.style.paddingLeft = makeEm(angleHeight / 2 + lineWeight); |
|
|
|
|
|
const viewBoxHeight = Math.floor(1000 * angleHeight * scale); |
|
const path = phasePath(viewBoxHeight); |
|
const svgNode = new SvgNode([new PathNode("phase", path)], { |
|
"width": "400em", |
|
"height": makeEm(viewBoxHeight / 1000), |
|
"viewBox": `0 0 400000 ${viewBoxHeight}`, |
|
"preserveAspectRatio": "xMinYMin slice", |
|
}); |
|
|
|
img = buildCommon.makeSvgSpan(["hide-tail"], [svgNode], options); |
|
img.style.height = makeEm(angleHeight); |
|
imgShift = inner.depth + lineWeight + clearance; |
|
|
|
} else { |
|
|
|
if (/cancel/.test(label)) { |
|
if (!isSingleChar) { |
|
inner.classes.push("cancel-pad"); |
|
} |
|
} else if (label === "angl") { |
|
inner.classes.push("anglpad"); |
|
} else { |
|
inner.classes.push("boxpad"); |
|
} |
|
|
|
|
|
let topPad = 0; |
|
let bottomPad = 0; |
|
let ruleThickness = 0; |
|
|
|
if (/box/.test(label)) { |
|
ruleThickness = Math.max( |
|
options.fontMetrics().fboxrule, |
|
options.minRuleThickness, |
|
); |
|
topPad = options.fontMetrics().fboxsep + |
|
(label === "colorbox" ? 0 : ruleThickness); |
|
bottomPad = topPad; |
|
} else if (label === "angl") { |
|
ruleThickness = Math.max( |
|
options.fontMetrics().defaultRuleThickness, |
|
options.minRuleThickness |
|
); |
|
topPad = 4 * ruleThickness; |
|
bottomPad = Math.max(0, 0.25 - inner.depth); |
|
} else { |
|
topPad = isSingleChar ? 0.2 : 0; |
|
bottomPad = topPad; |
|
} |
|
|
|
img = stretchy.encloseSpan(inner, label, topPad, bottomPad, options); |
|
if (/fbox|boxed|fcolorbox/.test(label)) { |
|
img.style.borderStyle = "solid"; |
|
img.style.borderWidth = makeEm(ruleThickness); |
|
} else if (label === "angl" && ruleThickness !== 0.049) { |
|
img.style.borderTopWidth = makeEm(ruleThickness); |
|
img.style.borderRightWidth = makeEm(ruleThickness); |
|
} |
|
imgShift = inner.depth + bottomPad; |
|
|
|
if (group.backgroundColor) { |
|
img.style.backgroundColor = group.backgroundColor; |
|
if (group.borderColor) { |
|
img.style.borderColor = group.borderColor; |
|
} |
|
} |
|
} |
|
|
|
let vlist; |
|
if (group.backgroundColor) { |
|
vlist = buildCommon.makeVList({ |
|
positionType: "individualShift", |
|
children: [ |
|
|
|
{type: "elem", elem: img, shift: imgShift}, |
|
{type: "elem", elem: inner, shift: 0}, |
|
], |
|
}, options); |
|
} else { |
|
const classes = /cancel|phase/.test(label) ? ["svg-align"] : []; |
|
vlist = buildCommon.makeVList({ |
|
positionType: "individualShift", |
|
children: [ |
|
|
|
{ |
|
type: "elem", |
|
elem: inner, |
|
shift: 0, |
|
}, |
|
{ |
|
type: "elem", |
|
elem: img, |
|
shift: imgShift, |
|
wrapperClasses: classes, |
|
}, |
|
], |
|
}, options); |
|
} |
|
|
|
if (/cancel/.test(label)) { |
|
|
|
|
|
vlist.height = inner.height; |
|
vlist.depth = inner.depth; |
|
} |
|
|
|
if (/cancel/.test(label) && !isSingleChar) { |
|
|
|
return buildCommon.makeSpan(["mord", "cancel-lap"], [vlist], options); |
|
} else { |
|
return buildCommon.makeSpan(["mord"], [vlist], options); |
|
} |
|
}; |
|
|
|
const mathmlBuilder = (group, options) => { |
|
let fboxsep = 0; |
|
const node = new mathMLTree.MathNode( |
|
(group.label.indexOf("colorbox") > -1) ? "mpadded" : "menclose", |
|
[mml.buildGroup(group.body, options)] |
|
); |
|
switch (group.label) { |
|
case "\\cancel": |
|
node.setAttribute("notation", "updiagonalstrike"); |
|
break; |
|
case "\\bcancel": |
|
node.setAttribute("notation", "downdiagonalstrike"); |
|
break; |
|
case "\\phase": |
|
node.setAttribute("notation", "phasorangle"); |
|
break; |
|
case "\\sout": |
|
node.setAttribute("notation", "horizontalstrike"); |
|
break; |
|
case "\\fbox": |
|
node.setAttribute("notation", "box"); |
|
break; |
|
case "\\angl": |
|
node.setAttribute("notation", "actuarial"); |
|
break; |
|
case "\\fcolorbox": |
|
case "\\colorbox": |
|
|
|
|
|
fboxsep = options.fontMetrics().fboxsep * |
|
options.fontMetrics().ptPerEm; |
|
node.setAttribute("width", `+${2 * fboxsep}pt`); |
|
node.setAttribute("height", `+${2 * fboxsep}pt`); |
|
node.setAttribute("lspace", `${fboxsep}pt`); |
|
node.setAttribute("voffset", `${fboxsep}pt`); |
|
if (group.label === "\\fcolorbox") { |
|
const thk = Math.max( |
|
options.fontMetrics().fboxrule, |
|
options.minRuleThickness, |
|
); |
|
node.setAttribute("style", "border: " + thk + "em solid " + |
|
String(group.borderColor)); |
|
} |
|
break; |
|
case "\\xcancel": |
|
node.setAttribute("notation", "updiagonalstrike downdiagonalstrike"); |
|
break; |
|
} |
|
if (group.backgroundColor) { |
|
node.setAttribute("mathbackground", group.backgroundColor); |
|
} |
|
return node; |
|
}; |
|
|
|
defineFunction({ |
|
type: "enclose", |
|
names: ["\\colorbox"], |
|
props: { |
|
numArgs: 2, |
|
allowedInText: true, |
|
argTypes: ["color", "text"], |
|
}, |
|
handler({parser, funcName}, args, optArgs) { |
|
const color = assertNodeType(args[0], "color-token").color; |
|
const body = args[1]; |
|
return { |
|
type: "enclose", |
|
mode: parser.mode, |
|
label: funcName, |
|
backgroundColor: color, |
|
body, |
|
}; |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
defineFunction({ |
|
type: "enclose", |
|
names: ["\\fcolorbox"], |
|
props: { |
|
numArgs: 3, |
|
allowedInText: true, |
|
argTypes: ["color", "color", "text"], |
|
}, |
|
handler({parser, funcName}, args, optArgs) { |
|
const borderColor = assertNodeType(args[0], "color-token").color; |
|
const backgroundColor = assertNodeType(args[1], "color-token").color; |
|
const body = args[2]; |
|
return { |
|
type: "enclose", |
|
mode: parser.mode, |
|
label: funcName, |
|
backgroundColor, |
|
borderColor, |
|
body, |
|
}; |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
defineFunction({ |
|
type: "enclose", |
|
names: ["\\fbox"], |
|
props: { |
|
numArgs: 1, |
|
argTypes: ["hbox"], |
|
allowedInText: true, |
|
}, |
|
handler({parser}, args) { |
|
return { |
|
type: "enclose", |
|
mode: parser.mode, |
|
label: "\\fbox", |
|
body: args[0], |
|
}; |
|
}, |
|
}); |
|
|
|
defineFunction({ |
|
type: "enclose", |
|
names: ["\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\phase"], |
|
props: { |
|
numArgs: 1, |
|
}, |
|
handler({parser, funcName}, args) { |
|
const body = args[0]; |
|
return { |
|
type: "enclose", |
|
mode: parser.mode, |
|
label: funcName, |
|
body, |
|
}; |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
defineFunction({ |
|
type: "enclose", |
|
names: ["\\angl"], |
|
props: { |
|
numArgs: 1, |
|
argTypes: ["hbox"], |
|
allowedInText: false, |
|
}, |
|
handler({parser}, args) { |
|
return { |
|
type: "enclose", |
|
mode: parser.mode, |
|
label: "\\angl", |
|
body: args[0], |
|
}; |
|
}, |
|
}); |
|
|