diff --git a/mathjax3-ts/output/chtml/FontData.ts b/mathjax3-ts/output/chtml/FontData.ts
index 782e82604..f0c22cb7c 100644
--- a/mathjax3-ts/output/chtml/FontData.ts
+++ b/mathjax3-ts/output/chtml/FontData.ts
@@ -197,7 +197,7 @@ export class FontData {
big_op_spacing1: .111,
big_op_spacing2: .167,
big_op_spacing3: .2,
- big_op_spacing4: .45, // .6, // better spacing for under arrows and braces
+ big_op_spacing4: .6,
big_op_spacing5: .1,
surd_height: .075,
diff --git a/mathjax3-ts/output/chtml/Wrapper.ts b/mathjax3-ts/output/chtml/Wrapper.ts
index ac049fb2f..59ba1ff74 100644
--- a/mathjax3-ts/output/chtml/Wrapper.ts
+++ b/mathjax3-ts/output/chtml/Wrapper.ts
@@ -59,6 +59,12 @@ export const FONTSIZE: StringMap = {
'249%': 'HG'
};
+export const SPACE: StringMap = {
+ [LENGTHS.em(3/18)]: '1',
+ [LENGTHS.em(4/18)]: '2',
+ [LENGTHS.em(5/18)]: '3',
+};
+
/*
* Needed to access node.style[id] using variable id
*/
@@ -102,6 +108,8 @@ export class CHTMLWrapper extends AbstractWrapper {
'mjx-box': {display: 'inline-block'},
'mjx-block': {display: 'block'},
'mjx-itable': {display: 'inline-table'},
+ 'mjx-row': {display: 'table-row'},
+ 'mjx-row > *': {display: 'table-cell'},
//
// These don't have Wrapper subclasses, so add their styles here
@@ -532,7 +540,12 @@ export class CHTMLWrapper extends AbstractWrapper {
*/
protected handleSpace() {
if (this.bbox.L) {
- this.chtml.setAttribute('space', (this.bbox.L * 18 - 2).toString());
+ const space = this.em(this.bbox.L);
+ if (SPACE[space]) {
+ this.chtml.setAttribute('space', SPACE[space]);
+ } else {
+ this.chtml.style.marginLeft = space;
+ }
}
}
diff --git a/mathjax3-ts/output/chtml/Wrappers.ts b/mathjax3-ts/output/chtml/Wrappers.ts
index 94b7391bf..e0fb7ebf8 100644
--- a/mathjax3-ts/output/chtml/Wrappers.ts
+++ b/mathjax3-ts/output/chtml/Wrappers.ts
@@ -30,6 +30,8 @@ import {CHTMLmrow, CHTMLinferredMrow} from './Wrappers/mrow.js';
import {CHTMLmfrac} from './Wrappers/mfrac.js';
import {CHTMLmsqrt} from './Wrappers/msqrt.js';
import {CHTMLmroot} from './Wrappers/mroot.js';
+import {CHTMLmsub, CHTMLmsup, CHTMLmsubsup} from './Wrappers/msubsup.js';
+import {CHTMLmover, CHTMLmunder, CHTMLmunderover} from './Wrappers/munderover.js';
import {CHTMLmtable} from './Wrappers/mtable.js';
import {CHTMLmtr, CHTMLmlabeledtr} from './Wrappers/mtr.js';
import {CHTMLmtd} from './Wrappers/mtd.js';
@@ -47,6 +49,12 @@ export const CHTMLWrappers: {[kind: string]: typeof CHTMLWrapper} = {
[CHTMLmfrac.kind]: CHTMLmfrac,
[CHTMLmsqrt.kind]: CHTMLmsqrt,
[CHTMLmroot.kind]: CHTMLmroot,
+ [CHTMLmsub.kind]: CHTMLmsub,
+ [CHTMLmsup.kind]: CHTMLmsup,
+ [CHTMLmsubsup.kind]: CHTMLmsubsup,
+ [CHTMLmunder.kind]: CHTMLmunder,
+ [CHTMLmover.kind]: CHTMLmover,
+ [CHTMLmunderover.kind]: CHTMLmunderover,
[CHTMLmtable.kind]: CHTMLmtable,
[CHTMLmtr.kind]: CHTMLmtr,
[CHTMLmlabeledtr.kind]: CHTMLmlabeledtr,
diff --git a/mathjax3-ts/output/chtml/Wrappers/mfrac.ts b/mathjax3-ts/output/chtml/Wrappers/mfrac.ts
index 1b6ad601a..e4fef84cc 100644
--- a/mathjax3-ts/output/chtml/Wrappers/mfrac.ts
+++ b/mathjax3-ts/output/chtml/Wrappers/mfrac.ts
@@ -37,28 +37,16 @@ export class CHTMLmfrac extends CHTMLWrapper {
public static kind = MmlMfrac.prototype.kind;
public static styles: StyleList = {
- 'mjx-strut': {
- display: 'inline-block',
- height: '1em',
- width: 0,
- 'vertical-align': '-.25em'
- },
- 'mjx-hstrut': {
+ 'mjx-frac': {
display: 'inline-block',
- height: '.75em',
- width: 0
+ 'vertical-align': '0.17em', // axis_height - 1.5 * rule_thickness
+ padding: '0 .22em' // nulldelimiterspace + .1 (for line's -.1em margin)
},
- 'mjx-dstrut': {
- display: 'inline-block',
- height: '.25em',
- width: 0,
- 'vertical-align': '-.25em'
+ 'mjx-frac[type="d"]': {
+ 'vertical-align': '.04em' // axis_height - 3.5 * rule_thickness
},
-
- 'mjx-frac': {
- display: 'inline-block',
- 'vertical-align': '0.145em',
- padding: '0 .1em'
+ 'mjx-frac[delims="true"]': {
+ padding: '0 .1em' // .1 (for line's -.1em margin)
},
'mjx-dtable': {
display: 'inline-table',
@@ -67,6 +55,10 @@ export class CHTMLmfrac extends CHTMLWrapper {
'mjx-dtable > *': {
'font-size': '2000%'
},
+ 'mjx-dbox': {
+ display: 'block',
+ 'font-size': '5%'
+ },
'mjx-row': {
display: 'table-row'
},
@@ -78,40 +70,68 @@ export class CHTMLmfrac extends CHTMLWrapper {
display: 'block',
'text-align': 'center'
},
- 'mjx-dbox': {
- display: 'block',
- 'font-size': '5%'
+
+ 'mjx-den[align="right"], mjx-num[align="right"]': {
+ align: 'right'
+ },
+ 'mjx-den[align="left"], mjx-num[align="left"]': {
+ align: 'left'
+ },
+
+ 'mjx-nstrut': {
+ display: 'inline-block',
+ height: '.054em', // num2 - a - 1.5t
+ width: 0,
+ 'vertical-align': '-.054em' // ditto
+ },
+ 'mjx-nstrut[type="d"]': {
+ height: '.217em', // num1 - a - 3.5t
+ 'vertical-align': '-.217em', // ditto
+ },
+ 'mjx-dstrut': {
+ display: 'inline-block',
+ height: '.505em', // denom2 + a - 1.5t
+ width: 0
+ },
+ 'mjx-dstrut[type="d"]': {
+ height: '.726em', // denom1 + a - 3.5t
},
'mjx-line': {
display: 'block',
'box-sizing': 'border-box',
'min-height': '1px',
- height: '.06em',
- 'border-top': '.06em solid',
- margin: '.06em -.1em',
+ height: '.06em', // t = rule_thickness
+ 'border-top': '.06em solid', // t
+ margin: '.06em -.1em', // t
overflow: 'hidden'
+ },
+ 'mjx-line[type="d"]': {
+ margin: '.18em -.1em' // 3t
}
+
};
/*
* @override
*/
public toCHTML(parent: HTMLElement) {
+ this.chtml = this.standardCHTMLnode(parent);
+ const attr = this.node.attributes.getList('displaystyle', 'scriptlevel');
+ const style = (attr.displaystyle && attr.scriptlevel === 0 ? {type: 'd'} : {});
+ const fstyle = (this.node.getProperty('withDelims') ? {...style, delims: 'true'} : style);
let num, den;
- let chtml = this.html('mjx-frac', {}, [
- num = this.html('mjx-num', {}, [this.html('mjx-dstrut')]),
+ this.chtml.appendChild(this.html('mjx-frac', fstyle, [
+ num = this.html('mjx-num', {}, [this.html('mjx-nstrut', style)]),
this.html('mjx-dbox', {}, [
this.html('mjx-dtable', {}, [
- this.html('mjx-line'),
+ this.html('mjx-line', style),
this.html('mjx-row', {}, [
- den = this.html('mjx-den', {}, [this.html('mjx-hstrut')])
+ den = this.html('mjx-den', {}, [this.html('mjx-dstrut', style)])
])
])
])
- ]);
- this.chtml = parent.appendChild(chtml);
- this.handleScale();
+ ]));
this.childNodes[0].toCHTML(num);
this.childNodes[1].toCHTML(den);
}
@@ -121,14 +141,16 @@ export class CHTMLmfrac extends CHTMLWrapper {
*/
public computeBBox(bbox: BBox) {
bbox.empty();
+ const attr = this.node.attributes.getList('displaystyle', 'scriptlevel');
+ const display = attr.displaystyle && attr.scriptlevel === 0;
const nbox = this.childNodes[0].getBBox();
const dbox = this.childNodes[1].getBBox();
- const pad = (this.node.getProperty('withDelims') as boolean ? this.font.params.nulldelimiterspace : 0);
+ const pad = (this.node.getProperty('withDelims') as boolean ? 0 : this.font.params.nulldelimiterspace);
const a = this.font.params.axis_height;
- const t = this.font.params.rule_thickness;
- bbox.combine(nbox, pad, a + 1.5 * t + Math.max(nbox.d, .25));
- bbox.combine(dbox, pad, a - 1.5 * t - Math.max(dbox.h, .75));
- bbox.w += pad + .2;
+ const T = (display ? 3.5 : 1.5) * this.font.params.rule_thickness;
+ bbox.combine(nbox, 0, a + T + Math.max(nbox.d * nbox.rscale, display ? .217 : .054));
+ bbox.combine(dbox, 0, a - T - Math.max(dbox.h * dbox.rscale, display ? .726 : .505));
+ bbox.w += 2 * pad + .2;
bbox.clean();
}
diff --git a/mathjax3-ts/output/chtml/Wrappers/msubsup.ts b/mathjax3-ts/output/chtml/Wrappers/msubsup.ts
new file mode 100644
index 000000000..6b3d0797e
--- /dev/null
+++ b/mathjax3-ts/output/chtml/Wrappers/msubsup.ts
@@ -0,0 +1,183 @@
+/*************************************************************
+ *
+ * Copyright (c) 2017 The MathJax Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview Implements the CHTMLmsubsup wrapper for the MmlMsubsup object
+ * and the special cases CHTMLmsub and CHTMLmsup
+ *
+ * @author dpvc@mathjax.org (Davide Cervone)
+ */
+
+import {CHTMLWrapper} from '../Wrapper.js';
+import {CHTMLscriptbase} from './scriptbase.js';
+import {MmlMsubsup, MmlMsub, MmlMsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
+import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
+import {BBox} from '../BBox.js';
+import {StyleList} from '../CssStyles.js';
+
+/*****************************************************************/
+/*
+ * The CHTMLmsub wrapper for the MmlMsub object
+ */
+
+export class CHTMLmsub extends CHTMLscriptbase {
+ public static kind = MmlMsub.prototype.kind;
+
+ /*
+ * @override
+ */
+ public get script() {
+ return this.childNodes[(this.node as MmlMsub).sub];
+ }
+
+ /*
+ * Get the shift for the subscript
+ *
+ * @override
+ */
+ protected getOffset(bbox: BBox, sbox: BBox) {
+ return -this.getV(bbox, sbox);
+ }
+
+}
+
+/*****************************************************************/
+/*
+ * The CHTMLmsup wrapper for the MmlMsup object
+ */
+
+export class CHTMLmsup extends CHTMLscriptbase {
+ public static kind = MmlMsup.prototype.kind;
+
+ /*
+ * @override
+ */
+ public get script() {
+ return this.childNodes[(this.node as MmlMsup).sup];
+ }
+
+ /*
+ * Get the shift for the superscript
+ *
+ * @override
+ */
+ public getOffset(bbox: BBox, sbox: BBox) {
+ return this.getU(bbox, sbox);
+ }
+
+}
+
+/*****************************************************************/
+/*
+ * The CHTMLmsubsup wrapper for the MmlMsubsup object
+ */
+
+export class CHTMLmsubsup extends CHTMLscriptbase {
+ public static kind = MmlMsubsup.prototype.kind;
+
+ public static styles: StyleList = {
+ 'mjx-script': {
+ display: 'inline-block',
+ 'padding-right': '.05em' // scriptspace
+ },
+ 'mjx-script > *': {
+ display: 'block'
+ }
+ };
+
+ /*
+ * Cached values for the script offsets and separation (so if they are
+ * computed in computeBBox(), they don't have to be recomputed for toCHTML())
+ */
+ protected UVQ: number[] = null;
+
+ /*
+ * @return{CHTMLWrapper} The wrapper for the subscript
+ */
+ public get subChild() {
+ return this.childNodes[(this.node as MmlMsubsup).sub];
+ }
+
+ /*
+ * @return{CHTMLWrapper} The wrapper for the superscript
+ */
+ public get supChild() {
+ return this.childNodes[(this.node as MmlMsubsup).sup];
+ }
+
+ /*
+ * @override
+ */
+ public toCHTML(parent: HTMLElement) {
+ this.chtml = this.standardCHTMLnode(parent);
+ const [u, v, q] = this.getUVQ(this.baseChild.getBBox(), this.subChild.getBBox(), this.supChild.getBBox());
+ const style = {'vertical-align': this.em(v)};
+ this.baseChild.toCHTML(this.chtml);
+ const stack = this.chtml.appendChild(this.html('mjx-script', {style}));
+ this.supChild.toCHTML(stack);
+ stack.appendChild(this.html('mjx-spacer', {style: {'margin-top': this.em(q)}}));
+ this.subChild.toCHTML(stack);
+ }
+
+ /*
+ * @override
+ */
+ public computeBBox(bbox: BBox) {
+ const basebox = this.baseChild.getBBox();
+ const subbox = this.subChild.getBBox();
+ const supbox = this.supChild.getBBox();
+ bbox.empty();
+ bbox.append(basebox);
+ const w = bbox.w;
+ const [u, v, q] = this.getUVQ(basebox, subbox, supbox);
+ bbox.combine(subbox, w, v);
+ bbox.combine(supbox, w, u);
+ bbox.w += this.font.params.scriptspace;
+ bbox.clean();
+ }
+
+ /*
+ * Get the shift for the scripts and their separation (TeXBook Appendix G 18adef)
+ *
+ * @param{BBox} basebox The bounding box of the base
+ * @param{BBox} subbox The bounding box of the superscript
+ * @param{BBox} supbox The bounding box of the subscript
+ * @return{number[]} The vertical offsets for super and subscripts, and the space between them
+ */
+ protected getUVQ(basebox: BBox, subbox: BBox, supbox: BBox) {
+ if (this.UVQ) return this.UVQ;
+ const tex = this.font.params;
+ const t = 3 * tex.rule_thickness;
+ let [u, v] = (this.isCharBase() ? [0, 0] : [this.getU(basebox, supbox),
+ Math.max(basebox.d + tex.sub_drop * subbox.rscale, tex.sub2)]);
+ let q = (u - supbox.d * supbox.rscale) - (subbox.h * subbox.rscale - v);
+ if (q < t) {
+ v += t - q;
+ const p = (4/5) * tex.x_height - (u - supbox.d * supbox.rscale);
+ if (p > 0) {
+ u += p;
+ v -= p;
+ }
+ }
+ u = Math.max(this.length2em(this.node.attributes.get('superscriptshift'), u), u);
+ v = Math.max(this.length2em(this.node.attributes.get('subscriptshift'), v), v);
+ q = (u - supbox.d * supbox.rscale) - (subbox.h * subbox.rscale - v);
+ this.UVQ = [u, -v, q];
+ return this.UVQ;
+ }
+
+}
diff --git a/mathjax3-ts/output/chtml/Wrappers/munderover.ts b/mathjax3-ts/output/chtml/Wrappers/munderover.ts
new file mode 100644
index 000000000..dfda6468e
--- /dev/null
+++ b/mathjax3-ts/output/chtml/Wrappers/munderover.ts
@@ -0,0 +1,274 @@
+/*************************************************************
+ *
+ * Copyright (c) 2017 The MathJax Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview Implements the CHTMLmunderover wrapper for the MmlMunderover object
+ * and the special cases CHTMLmunder and CHTMLmsup
+ *
+ * @author dpvc@mathjax.org (Davide Cervone)
+ */
+
+import {CHTMLWrapper} from '../Wrapper.js';
+import {CHTMLmsubsup, CHTMLmsub, CHTMLmsup} from './msubsup.js';
+import {MmlMunderover, MmlMunder, MmlMover} from '../../../core/MmlTree/MmlNodes/munderover.js';
+import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
+import {BBox} from '../BBox.js';
+import {StyleList} from '../CssStyles.js';
+
+/*****************************************************************/
+/*
+ * The CHTMLmunder wrapper for the MmlMunder object
+ */
+
+export class CHTMLmunder extends CHTMLmsub {
+ public static kind = MmlMunder.prototype.kind;
+
+ public static styles: StyleList = {
+ 'mjx-munder:not([limits="false"])': {
+ display: 'inline-table',
+ },
+ 'mjx-munder > mjx-row': {
+ 'text-align': 'left'
+ },
+ 'mjx-under': {
+ 'padding-bottom': '.1em' // big_op_spacing5
+ }
+ };
+
+ /*
+ * @override
+ */
+ public get script() {
+ return this.childNodes[(this.node as MmlMunder).under];
+ }
+
+ /*
+ * @override
+ */
+ public toCHTML(parent: HTMLElement) {
+ if (this.hasMovableLimits()) {
+ super.toCHTML(parent);
+ this.chtml.setAttribute('limits', 'false');
+ return;
+ }
+ this.chtml = this.standardCHTMLnode(parent);
+ const base = this.chtml.appendChild(this.html('mjx-row')).appendChild(this.html('mjx-base'));
+ const under = this.chtml.appendChild(this.html('mjx-row')).appendChild(this.html('mjx-under'));
+ this.baseChild.toCHTML(base);
+ this.script.toCHTML(under);
+ const basebox = this.baseChild.getBBox();
+ const underbox = this.script.getBBox();
+ const [k, v] = this.getUnderKV(basebox, underbox);
+ under.style.paddingTop = this.em(k);
+ this.setDeltaW([base, under], this.getDeltaW([basebox, underbox]));
+ }
+
+ /*
+ * @override
+ */
+ public computeBBox(bbox: BBox) {
+ if (this.hasMovableLimits()) {
+ super.computeBBox(bbox);
+ return;
+ }
+ bbox.empty();
+ const basebox = this.baseChild.getBBox();
+ const underbox = this.script.getBBox();
+ const [k, v] = this.getUnderKV(basebox, underbox);
+ const [bw, uw] = this.getDeltaW([basebox, underbox]);
+ bbox.combine(basebox, bw, 0);
+ bbox.combine(underbox, uw, v);
+ bbox.d += this.font.params.big_op_spacing5;
+ bbox.clean();
+ }
+
+}
+
+/*****************************************************************/
+/*
+ * The CHTMLmover wrapper for the MmlMover object
+ */
+
+export class CHTMLmover extends CHTMLmsup {
+ public static kind = MmlMover.prototype.kind;
+
+ public static styles: StyleList = {
+ 'mjx-mover:not([limits="false"])': {
+ 'padding-top': '.1em' // big_op_spacing5
+ },
+ 'mjx-mover:not([limits="false"]) > *': {
+ display: 'block',
+ 'text-align': 'left'
+ }
+ };
+
+ /*
+ * @override
+ */
+ public get script() {
+ return this.childNodes[(this.node as MmlMover).over];
+ }
+
+ /*
+ * @override
+ */
+ public toCHTML(parent: HTMLElement) {
+ if (this.hasMovableLimits()) {
+ super.toCHTML(parent);
+ this.chtml.setAttribute('limits', 'false');
+ return;
+ }
+ this.chtml = this.standardCHTMLnode(parent);
+ const over = this.chtml.appendChild(this.html('mjx-over'));
+ const base = this.chtml.appendChild(this.html('mjx-base'));
+ this.script.toCHTML(over);
+ this.baseChild.toCHTML(base);
+ const overbox = this.script.getBBox();
+ const basebox = this.baseChild.getBBox();
+ const [k, u] = this.getOverKU(basebox, overbox);
+ over.style.paddingBottom = this.em(k);
+ this.setDeltaW([base, over], this.getDeltaW([basebox, overbox]));
+ if (overbox.d < 0) {
+ over.style.marginBottom = this.em(overbox.d * overbox.rscale);
+ }
+ }
+
+ /*
+ * @override
+ */
+ public computeBBox(bbox: BBox) {
+ if (this.hasMovableLimits()) {
+ super.computeBBox(bbox);
+ return;
+ }
+ bbox.empty();
+ const basebox = this.baseChild.getBBox();
+ const overbox = this.script.getBBox();
+ const [k, u] = this.getOverKU(basebox, overbox);
+ const [bw, ow] = this.getDeltaW([basebox, overbox]);
+ bbox.combine(basebox, bw, 0);
+ bbox.combine(overbox, ow, u);
+ bbox.h += this.font.params.big_op_spacing5;
+ bbox.clean();
+ }
+
+}
+
+/*****************************************************************/
+/*
+ * The CHTMLmunderover wrapper for the MmlMunderover object
+ */
+
+export class CHTMLmunderover extends CHTMLmsubsup {
+ public static kind = MmlMunderover.prototype.kind;
+
+ public static styles: StyleList = {
+ 'mjx-munderover:not([limits="false"])': {
+ 'padding-top': '.1em' // big_op_spacing5
+ },
+ 'mjx-munderover:not([limits="false"]) > *': {
+ display: 'block'
+ },
+ };
+
+ /*
+ * @return{CHTMLWrapper) The wrapped under node
+ */
+ public get underChild() {
+ return this.childNodes[(this.node as MmlMunderover).under];
+ }
+
+ /*
+ * @return{CHTMLWrapper) The wrapped overder node
+ */
+ public get overChild() {
+ return this.childNodes[(this.node as MmlMunderover).over];
+ }
+
+ /*
+ * Needed for movablelimits
+ *
+ * @override
+ */
+ public get subChild() {
+ return this.underChild;
+ }
+
+ /*
+ * Needed for movablelimits
+ *
+ * @override
+ */
+ public get supChild() {
+ return this.overChild;
+ }
+
+ /*
+ * @override
+ */
+ public toCHTML(parent: HTMLElement) {
+ if (this.hasMovableLimits()) {
+ super.toCHTML(parent);
+ this.chtml.setAttribute('limits', 'false');
+ return;
+ }
+ this.chtml = this.standardCHTMLnode(parent);
+ const over = this.chtml.appendChild(this.html('mjx-over'));
+ const table = this.chtml.appendChild(this.html('mjx-box')).appendChild(this.html('mjx-munder'));
+ const base = table.appendChild(this.html('mjx-row')).appendChild(this.html('mjx-base'));
+ const under = table.appendChild(this.html('mjx-row')).appendChild(this.html('mjx-under'));
+ this.overChild.toCHTML(over);
+ this.baseChild.toCHTML(base);
+ this.underChild.toCHTML(under);
+ const overbox = this.overChild.getBBox();
+ const basebox = this.baseChild.getBBox();
+ const underbox = this.underChild.getBBox();
+ const [ok, u] = this.getOverKU(basebox, overbox);
+ const [uk, v] = this.getUnderKV(basebox, underbox);
+ over.style.paddingBottom = this.em(ok);
+ under.style.paddingTop = this.em(uk);
+ this.setDeltaW([base, under, over], this.getDeltaW([basebox, underbox, overbox]));
+ if (overbox.d < 0) {
+ over.style.marginBottom = this.em(overbox.d * overbox.rscale);
+ }
+ }
+
+ /*
+ * @override
+ */
+ public computeBBox(bbox: BBox) {
+ if (this.hasMovableLimits()) {
+ super.computeBBox(bbox);
+ return;
+ }
+ bbox.empty();
+ const overbox = this.overChild.getBBox();
+ const basebox = this.baseChild.getBBox();
+ const underbox = this.underChild.getBBox();
+ const [ok, u] = this.getOverKU(basebox, overbox);
+ const [uk, v] = this.getUnderKV(basebox, underbox);
+ const [bw, uw, ow] = this.getDeltaW([basebox, underbox, overbox]);
+ bbox.combine(basebox, bw, 0);
+ bbox.combine(overbox, ow, u);
+ bbox.combine(underbox, uw, v);
+ const z = this.font.params.big_op_spacing5;
+ bbox.h += z;
+ bbox.d += z;
+ bbox.clean();
+ }
+
+}
diff --git a/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts b/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts
new file mode 100644
index 000000000..de3b62783
--- /dev/null
+++ b/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts
@@ -0,0 +1,214 @@
+/*************************************************************
+ *
+ * Copyright (c) 2017 The MathJax Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview Implements the a base class for CHTMLmsubsup and CHTMLmunderover
+ * and their relatives. (Since munderover can become msubsup
+ * when movablelimits is set, munderoer needs to be able to
+ * do the same thing as msubsup in some cases.)
+ *
+ * @author dpvc@mathjax.org (Davide Cervone)
+ */
+
+import {CHTMLWrapper} from '../Wrapper.js';
+import {MmlMsubsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
+import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
+import {BBox} from '../BBox.js';
+import {StyleList} from '../CssStyles.js';
+
+/*****************************************************************/
+/*
+ * A base class for msup/msub/msubsup and munder/mover/munderover
+ * wrapper implementations
+ */
+
+export class CHTMLscriptbase extends CHTMLWrapper {
+ public static kind = 'scriptbase';
+
+ /*
+ * @return{CHTMLWrapper} The base element's wrapper
+ */
+ public get baseChild() {
+ return this.childNodes[(this.node as MmlMsubsup).base];
+ }
+
+ /*
+ * @return{CHTMLWrapper} The script element's wrapper (overridden in subclasses)
+ */
+ public get script() {
+ return this.childNodes[1];
+ }
+
+ /*
+ * This gives the common output for msub and msup. It is overriden
+ * for all the others (msubsup, munder, mover, munderover).
+ *
+ * @override
+ */
+ public toCHTML(parent: HTMLElement) {
+ this.chtml = this.standardCHTMLnode(parent);
+ const v = this.getOffset(this.baseChild.getBBox(), this.script.getBBox());
+ const style = {'vertical-align': this.em(v)};
+ this.baseChild.toCHTML(this.chtml);
+ this.script.toCHTML(this.chtml.appendChild(this.html('mjx-script', {style})));
+ }
+
+ /*
+ * This gives the common bbox for msub and msup. It is overriden
+ * for all the others (msubsup, munder, mover, munderover).
+ *
+ * @override
+ */
+ public computeBBox(bbox: BBox) {
+ const basebox = this.baseChild.getBBox();
+ const scriptbox = this.script.getBBox();
+ bbox.append(basebox);
+ bbox.combine(scriptbox, bbox.w, this.getOffset(basebox, scriptbox));
+ bbox.w += this.font.params.scriptspace;
+ bbox.clean();
+ }
+
+ /*
+ * @return{boolean} True if the base is an mi, mn, or mo (not a largeop) consisting of a single character
+ */
+ protected isCharBase() {
+ let base = this.baseChild;
+ if ((base.node.isKind('mstyle') || base.node.isKind('mrow')) && base.childNodes.length === 1) {
+ base = base.childNodes[0];
+ }
+ return ((base.node.isKind('mo') || base.node.isKind('mi') || base.node.isKind('mn')) &&
+ base.bbox.rscale === 1 && base.getText().length === 1 &&
+ !base.node.attributes.get('largeop'));
+ }
+
+ /***************************************************************************/
+ /*
+ * Methods for sub-sup nodes
+ */
+
+ /*
+ * Get the shift for the script (implemented in subclasses)
+ *
+ * @param{BBox} bbox The bounding box of the base element
+ * @param{BBox} sbox The bounding box of the script element
+ * @return{number} The vertical offset for the script
+ */
+ protected getOffset(bbox: BBox, sbox: BBox) {
+ return 0;
+ }
+
+ /*
+ * Get the shift for a subscript (TeXBook Appendix G 18ab)
+ *
+ * @param{BBox} bbox The bounding box of the base element
+ * @param{BBox} sbox The bounding box of the superscript element
+ * @return{number} The vertical offset for the script
+ */
+ protected getV(bbox: BBox, sbox: BBox) {
+ const tex = this.font.params;
+ const subscriptshift = this.length2em(this.node.attributes.get('subscriptshift'), tex.sub1);
+ return Math.max(
+ this.isCharBase() ? 0 : bbox.d + tex.sub_drop * sbox.rscale,
+ subscriptshift,
+ sbox.h * sbox.rscale - (4/5) * tex.x_height
+ );
+ }
+
+ /*
+ * Get the shift for a superscript (TeXBook Appendix G 18acd)
+ *
+ * @param{BBox} bbox The bounding box of the base element
+ * @param{BBox} sbox The bounding box of the superscript element
+ * @return{number} The vertical offset for the script
+ */
+ protected getU(bbox: BBox, sbox: BBox) {
+ const tex = this.font.params;
+ const attr = this.node.attributes.getList('displaystyle', 'texprimestyle', 'superscriptshift');
+ const p = (attr.displaystyle ? tex.sup1 : attr.texprimestyle ? tex.sup3 : tex.sup2);
+ const superscriptshift = this.length2em(attr.superscriptshift, p);
+ return Math.max(
+ this.isCharBase() ? 0 : bbox.h - tex.sup_drop * sbox.rscale,
+ superscriptshift,
+ sbox.d * sbox.rscale + (1/4) * tex.x_height
+ );
+ }
+
+ /***************************************************************************/
+ /*
+ * Methods for under-over nodes
+ */
+
+ /*
+ * @return{boolean} True if the base has movablelimits (needed by munderover)
+ */
+ protected hasMovableLimits() {
+ const display = this.node.attributes.get('displaystyle');
+ return (!display && (this.node.getProperty('movablelimits') ||
+ this.node.attributes.get('movablelimits') ||
+ this.baseChild.coreMO().node.attributes.get('movablelimits')));
+ }
+
+ /*
+ * Get the separation and offset for overscripts (TeXBoox Appendix G 13, 13a)
+ *
+ * @param{BBox} basebox The bounding box of the base
+ * @param{BBox} overbox The bounding box of the overscript
+ * @return{numner[]} The separation between their boxes, and the offset of the overscript
+ */
+ protected getOverKU(basebox: BBox, overbox: BBox) {
+ const tex = this.font.params;
+ const d = overbox.d * overbox.rscale;
+ const k = Math.max(tex.big_op_spacing1, tex.big_op_spacing3 - Math.max(0, d));
+ return [k, basebox.h + k + d];
+ }
+
+ /*
+ * Get the separation and offset for underscripts (TeXBoox Appendix G 13, 13a)
+ *
+ * @param{BBox} basebox The bounding box of the base
+ * @param{BBox} underbox The bounding box of the underscript
+ * @return{numner[]} The separation between their boxes, and the offset of the underscript
+ */
+ protected getUnderKV(basebox: BBox, underbox: BBox) {
+ const tex = this.font.params;
+ const h = underbox.h * underbox.rscale;
+ const k = Math.max(tex.big_op_spacing2, tex.big_op_spacing4 - h);
+ return [k, -(basebox.d + k + h)];
+ }
+
+ /*
+ * @param{BBox[]} boxes The bounding boxes whose offsets are to be computed
+ * @param{number[]} The x offsets of the boxes to center them in a vertical stack
+ */
+ protected getDeltaW(boxes: BBox[]) {
+ const widths = boxes.map(box => box.w * box.rscale);
+ const w = Math.max(...widths);
+ return widths.map(width => (w - width) / 2);
+ }
+
+ /*
+ * @param{HTMLElement[]} nodes The HTML elements to be centered in a stack
+ * @param{number[]} dx The x offsets needed to center the elements
+ */
+ protected setDeltaW(nodes: HTMLElement[], dx: number[]) {
+ for (let i = 0; i < dx.length; i++) {
+ if (dx[i]) {
+ nodes[i].style.paddingLeft = this.em(dx[i]);
+ }
+ }
+ }
+}
diff --git a/mathjax3-ts/output/chtml/fonts/tex.ts b/mathjax3-ts/output/chtml/fonts/tex.ts
index 022a27334..f671d5f66 100644
--- a/mathjax3-ts/output/chtml/fonts/tex.ts
+++ b/mathjax3-ts/output/chtml/fonts/tex.ts
@@ -434,7 +434,7 @@ export class TeXFont extends FontData {
css.width = this.em(w);
}
if (options.css & CSS.padding) {
- css.padding = this.em(h) + ' 0 ' + this.em(d);
+ css.padding = this.em(Math.max(0, h)) + ' 0 ' + this.em(Math.max(0, d));
}
if (options.css & CSS.content) {
css.content = '"' + (options.c || this.char(n, true)) + '"';