|
|
|
import defineFunction, {normalizeArgument} from "../defineFunction"; |
|
import buildCommon from "../buildCommon"; |
|
import mathMLTree from "../mathMLTree"; |
|
import utils from "../utils"; |
|
import stretchy from "../stretchy"; |
|
import {assertNodeType} from "../parseNode"; |
|
import {assertSpan, assertSymbolDomNode} from "../domTree"; |
|
import {makeEm} from "../units"; |
|
|
|
import * as html from "../buildHTML"; |
|
import * as mml from "../buildMathML"; |
|
|
|
import type {ParseNode, AnyParseNode} from "../parseNode"; |
|
import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction"; |
|
|
|
|
|
|
|
export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => { |
|
|
|
let base: AnyParseNode; |
|
let group: ParseNode<"accent">; |
|
|
|
let supSubGroup; |
|
if (grp && grp.type === "supsub") { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
group = assertNodeType(grp.base, "accent"); |
|
|
|
base = group.base; |
|
|
|
grp.base = base; |
|
|
|
|
|
|
|
supSubGroup = assertSpan(html.buildGroup(grp, options)); |
|
|
|
|
|
grp.base = group; |
|
} else { |
|
group = assertNodeType(grp, "accent"); |
|
base = group.base; |
|
} |
|
|
|
|
|
const body = html.buildGroup(base, options.havingCrampedStyle()); |
|
|
|
|
|
const mustShift = group.isShifty && utils.isCharacterBox(base); |
|
|
|
|
|
|
|
|
|
|
|
|
|
let skew = 0; |
|
if (mustShift) { |
|
|
|
|
|
const baseChar = utils.getBaseElem(base); |
|
|
|
const baseGroup = html.buildGroup(baseChar, options.havingCrampedStyle()); |
|
|
|
skew = assertSymbolDomNode(baseGroup).skew; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
const accentBelow = group.label === "\\c"; |
|
|
|
|
|
let clearance = accentBelow |
|
? body.height + body.depth |
|
: Math.min( |
|
body.height, |
|
options.fontMetrics().xHeight); |
|
|
|
|
|
let accentBody; |
|
if (!group.isStretchy) { |
|
let accent; |
|
let width: number; |
|
if (group.label === "\\vec") { |
|
|
|
|
|
|
|
|
|
|
|
accent = buildCommon.staticSvg("vec", options); |
|
width = buildCommon.svgData.vec[1]; |
|
} else { |
|
accent = buildCommon.makeOrd({mode: group.mode, text: group.label}, |
|
options, "textord"); |
|
accent = assertSymbolDomNode(accent); |
|
|
|
|
|
accent.italic = 0; |
|
width = accent.width; |
|
if (accentBelow) { |
|
clearance += accent.depth; |
|
} |
|
} |
|
|
|
accentBody = buildCommon.makeSpan(["accent-body"], [accent]); |
|
|
|
|
|
|
|
|
|
const accentFull = (group.label === "\\textcircled"); |
|
if (accentFull) { |
|
accentBody.classes.push('accent-full'); |
|
clearance = body.height; |
|
} |
|
|
|
|
|
let left = skew; |
|
|
|
|
|
|
|
|
|
|
|
if (!accentFull) { |
|
left -= width / 2; |
|
} |
|
|
|
accentBody.style.left = makeEm(left); |
|
|
|
|
|
|
|
if (group.label === "\\textcircled") { |
|
accentBody.style.top = ".2em"; |
|
} |
|
|
|
accentBody = buildCommon.makeVList({ |
|
positionType: "firstBaseline", |
|
children: [ |
|
{type: "elem", elem: body}, |
|
{type: "kern", size: -clearance}, |
|
{type: "elem", elem: accentBody}, |
|
], |
|
}, options); |
|
|
|
} else { |
|
accentBody = stretchy.svgSpan(group, options); |
|
|
|
accentBody = buildCommon.makeVList({ |
|
positionType: "firstBaseline", |
|
children: [ |
|
{type: "elem", elem: body}, |
|
{ |
|
type: "elem", |
|
elem: accentBody, |
|
wrapperClasses: ["svg-align"], |
|
wrapperStyle: skew > 0 |
|
? { |
|
width: `calc(100% - ${makeEm(2 * skew)})`, |
|
marginLeft: makeEm(2 * skew), |
|
} |
|
: undefined, |
|
}, |
|
], |
|
}, options); |
|
} |
|
|
|
const accentWrap = |
|
buildCommon.makeSpan(["mord", "accent"], [accentBody], options); |
|
|
|
if (supSubGroup) { |
|
|
|
|
|
supSubGroup.children[0] = accentWrap; |
|
|
|
|
|
|
|
supSubGroup.height = Math.max(accentWrap.height, supSubGroup.height); |
|
|
|
|
|
supSubGroup.classes[0] = "mord"; |
|
|
|
return supSubGroup; |
|
} else { |
|
return accentWrap; |
|
} |
|
}; |
|
|
|
const mathmlBuilder: MathMLBuilder<"accent"> = (group, options) => { |
|
const accentNode = |
|
group.isStretchy ? |
|
stretchy.mathMLnode(group.label) : |
|
new mathMLTree.MathNode("mo", [mml.makeText(group.label, group.mode)]); |
|
|
|
const node = new mathMLTree.MathNode( |
|
"mover", |
|
[mml.buildGroup(group.base, options), accentNode]); |
|
|
|
node.setAttribute("accent", "true"); |
|
|
|
return node; |
|
}; |
|
|
|
const NON_STRETCHY_ACCENT_REGEX = new RegExp([ |
|
"\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve", |
|
"\\check", "\\hat", "\\vec", "\\dot", "\\mathring", |
|
].map(accent => `\\${accent}`).join("|")); |
|
|
|
|
|
defineFunction({ |
|
type: "accent", |
|
names: [ |
|
"\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve", |
|
"\\check", "\\hat", "\\vec", "\\dot", "\\mathring", "\\widecheck", |
|
"\\widehat", "\\widetilde", "\\overrightarrow", "\\overleftarrow", |
|
"\\Overrightarrow", "\\overleftrightarrow", "\\overgroup", |
|
"\\overlinesegment", "\\overleftharpoon", "\\overrightharpoon", |
|
], |
|
props: { |
|
numArgs: 1, |
|
}, |
|
handler: (context, args) => { |
|
const base = normalizeArgument(args[0]); |
|
|
|
const isStretchy = !NON_STRETCHY_ACCENT_REGEX.test(context.funcName); |
|
const isShifty = !isStretchy || |
|
context.funcName === "\\widehat" || |
|
context.funcName === "\\widetilde" || |
|
context.funcName === "\\widecheck"; |
|
|
|
return { |
|
type: "accent", |
|
mode: context.parser.mode, |
|
label: context.funcName, |
|
isStretchy: isStretchy, |
|
isShifty: isShifty, |
|
base: base, |
|
}; |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|
|
|
|
defineFunction({ |
|
type: "accent", |
|
names: [ |
|
"\\'", "\\`", "\\^", "\\~", "\\=", "\\u", "\\.", '\\"', |
|
"\\c", "\\r", "\\H", "\\v", "\\textcircled", |
|
], |
|
props: { |
|
numArgs: 1, |
|
allowedInText: true, |
|
allowedInMath: true, |
|
argTypes: ["primitive"], |
|
}, |
|
handler: (context, args) => { |
|
const base = args[0]; |
|
let mode = context.parser.mode; |
|
|
|
if (mode === "math") { |
|
context.parser.settings.reportNonstrict("mathVsTextAccents", |
|
`LaTeX's accent ${context.funcName} works only in text mode`); |
|
mode = "text"; |
|
} |
|
|
|
return { |
|
type: "accent", |
|
mode: mode, |
|
label: context.funcName, |
|
isStretchy: false, |
|
isShifty: true, |
|
base: base, |
|
}; |
|
}, |
|
htmlBuilder, |
|
mathmlBuilder, |
|
}); |
|
|