diff --git a/src/ParseNode.js b/src/ParseNode.js index 114b9c58..3b74107a 100644 --- a/src/ParseNode.js +++ b/src/ParseNode.js @@ -68,6 +68,7 @@ type ParseNodeTypes = { limits: boolean, symbol: boolean, alwaysHandleSupSub?: boolean, + suppressBaseShift?: boolean, body?: string, value?: ParseNode<*>[], |}, diff --git a/src/functions.js b/src/functions.js index 681b29bd..ca36276c 100644 --- a/src/functions.js +++ b/src/functions.js @@ -70,26 +70,48 @@ defineFunction("mclass", [ defineFunction("mclass", ["\\stackrel", "\\overset", "\\underset"], { numArgs: 2, }, function(context, args) { - const mathAxisArg = args[1]; + const baseArg = args[1]; const shiftedArg = args[0]; - const xAxisOp = new ParseNode("op", { + let mclass = "mrel"; // default. May change below. + if (context.funcName !== "\\stackrel") { + // LaTeX applies \binrel spacing to \overset and \underset. + // \binrel spacing varies with (bin|rel|ord) of the atom in the argument. + // We'll do the same. + let atomType = ""; + if (baseArg.type === "ordgroup") { + atomType = baseArg.value[0].type; + } else { + atomType = baseArg.type; + } + if (/^(bin|rel)$/.test(atomType)) { + mclass = "m" + atomType; + } else { + // This may capture some instances in which the baseArg is more than + // just a single symbol. Say a \overset inside an \overset. + // TODO: A more comprehensive way to determine the baseArg type. + mclass = "mord"; + } + } + + const baseOp = new ParseNode("op", { type: "op", limits: true, alwaysHandleSupSub: true, symbol: false, - value: ordargument(mathAxisArg), - }, mathAxisArg.mode); + suppressBaseShift: context.funcName !== "\\stackrel", + value: ordargument(baseArg), + }, baseArg.mode); const supsub = new ParseNode("supsub", { - base: xAxisOp, + base: baseOp, sup: context.funcName === "\\underset" ? null : shiftedArg, sub: context.funcName === "\\underset" ? shiftedArg : null, }, shiftedArg.mode); return { type: "mclass", - mclass: "mrel", + mclass: mclass, value: [supsub], }; }); diff --git a/src/functions/op.js b/src/functions/op.js index ca74ccd5..a3e0a67a 100644 --- a/src/functions/op.js +++ b/src/functions/op.js @@ -72,8 +72,9 @@ const htmlBuilder = (group, options) => { // If content of op is a single symbol, shift it vertically. let baseShift = 0; let slant = 0; - if (base instanceof domTree.symbolNode) { - // Shift the symbol so its center lies on the axis (rule 13). It + if (base instanceof domTree.symbolNode && !group.value.suppressBaseShift) { + // We suppress the shift of the base of \overset and \underset. Otherwise, + // shift the symbol so its center lies on the axis (rule 13). It // appears that our fonts have the centers of the symbols already // almost on the axis, so these numbers are very small. Note we // don't actually apply this here, but instead it is used either in diff --git a/test/screenshotter/images/OverUnderset-chrome.png b/test/screenshotter/images/OverUnderset-chrome.png index 8dec19b2..1d013b55 100644 Binary files a/test/screenshotter/images/OverUnderset-chrome.png and b/test/screenshotter/images/OverUnderset-chrome.png differ diff --git a/test/screenshotter/images/OverUnderset-firefox.png b/test/screenshotter/images/OverUnderset-firefox.png index 83f7491a..51a18f02 100644 Binary files a/test/screenshotter/images/OverUnderset-firefox.png and b/test/screenshotter/images/OverUnderset-firefox.png differ diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml index ec9ae14e..3ecfecf9 100644 --- a/test/screenshotter/ss_data.yaml +++ b/test/screenshotter/ss_data.yaml @@ -218,7 +218,7 @@ OpLimits: | OverUnderline: x\underline{x}\underline{\underline{x}}\underline{x_{x_{x_x}}}\underline{x^{x^{x^x}}}\overline{x}\overline{x}\overline{x^{x^{x^x}}} \blue{\overline{\underline{x}}\underline{\overline{x}}} OverUnderset: | \begin{array}{l} - x\overset?=1 \quad \underset{*}{x}^2 \\ + x\overset?=1 \quad \underset{*}{x}^2 \quad \overset{a}{b}b\underset{a}{b}b \\ {\displaystyle\lim_{t\underset{>0}\to0}}\\ a+b+c+d\overset{b+c=0}\longrightarrow a+d\\ \overset { x = y } { \sqrt { a b } }