From 7ffb8b14cb9dbdbbbdb81047c21846d6acd67487 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Tue, 19 Sep 2017 21:21:21 -0400 Subject: [PATCH 1/6] Make fractions conform to TeX spacing, for all styles (display, text, script, and scriptscript). Still need to handle linethickness (particularly linethickness=0), and beveled fractions, but good enough for now. --- mathjax3-ts/output/chtml/Wrapper.ts | 13 ++- mathjax3-ts/output/chtml/Wrappers/mfrac.ts | 96 +++++++++++++--------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/mathjax3-ts/output/chtml/Wrapper.ts b/mathjax3-ts/output/chtml/Wrapper.ts index ac049fb2f..35ae30aee 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 */ @@ -532,7 +538,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/mfrac.ts b/mathjax3-ts/output/chtml/Wrappers/mfrac.ts index 1b6ad601a..ccdfff66e 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(); } From 11b05711b54da5028df45bfea1fc1f6ca5ed31f5 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 20 Sep 2017 06:40:18 -0400 Subject: [PATCH 2/6] Add support for mub/msup/msubsup --- mathjax3-ts/output/chtml/Wrappers.ts | 4 + mathjax3-ts/output/chtml/Wrappers/msubsup.ts | 183 ++++++++++++++++++ .../output/chtml/Wrappers/scriptbase.ts | 145 ++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 mathjax3-ts/output/chtml/Wrappers/msubsup.ts create mode 100644 mathjax3-ts/output/chtml/Wrappers/scriptbase.ts diff --git a/mathjax3-ts/output/chtml/Wrappers.ts b/mathjax3-ts/output/chtml/Wrappers.ts index 94b7391bf..212bebafb 100644 --- a/mathjax3-ts/output/chtml/Wrappers.ts +++ b/mathjax3-ts/output/chtml/Wrappers.ts @@ -30,6 +30,7 @@ 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 {CHTMLmtable} from './Wrappers/mtable.js'; import {CHTMLmtr, CHTMLmlabeledtr} from './Wrappers/mtr.js'; import {CHTMLmtd} from './Wrappers/mtd.js'; @@ -47,6 +48,9 @@ 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, [CHTMLmtable.kind]: CHTMLmtable, [CHTMLmtr.kind]: CHTMLmtr, [CHTMLmlabeledtr.kind]: CHTMLmlabeledtr, diff --git a/mathjax3-ts/output/chtml/Wrappers/msubsup.ts b/mathjax3-ts/output/chtml/Wrappers/msubsup.ts new file mode 100644 index 000000000..b05f3a91a --- /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 sub() { + return this.childNodes[(this.node as MmlMsubsup).sub]; + } + + /* + * @return{CHTMLWrapper} The wrapper for the superscript + */ + public get sup() { + 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.base.getBBox(), this.sub.getBBox(), this.sup.getBBox()); + const style = {'vertical-align': this.em(v)}; + this.base.toCHTML(this.chtml); + const stack = this.chtml.appendChild(this.html('mjx-script', {style})); + this.sup.toCHTML(stack); + stack.appendChild(this.html('mjx-spacer', {style: {'margin-top': this.em(q)}})); + this.sub.toCHTML(stack); + } + + /* + * @override + */ + public computeBBox(bbox: BBox) { + const basebox = this.base.getBBox(); + const subbox = this.sub.getBBox(); + const supbox = this.sup.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/scriptbase.ts b/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts new file mode 100644 index 000000000..bae7a7a17 --- /dev/null +++ b/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts @@ -0,0 +1,145 @@ +/************************************************************* + * + * 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 base() { + 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.base.getBBox(), this.script.getBBox()); + const style = {'vertical-align': this.em(v)}; + this.base.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.base.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(); + } + + /* + * 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 + ); + } + + /* + * @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.base; + 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')); + } + +} From ffeb86b5cfe6ec70cd37c0e7aa7144e44d45540c Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Wed, 20 Sep 2017 21:55:32 -0400 Subject: [PATCH 3/6] Add support for munder/mover/munderover --- mathjax3-ts/output/chtml/FontData.ts | 2 +- mathjax3-ts/output/chtml/Wrapper.ts | 2 + mathjax3-ts/output/chtml/Wrappers.ts | 4 + .../output/chtml/Wrappers/munderover.ts | 274 ++++++++++++++++++ .../output/chtml/Wrappers/scriptbase.ts | 87 +++++- mathjax3-ts/output/chtml/fonts/tex.ts | 2 +- 6 files changed, 360 insertions(+), 11 deletions(-) create mode 100644 mathjax3-ts/output/chtml/Wrappers/munderover.ts 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 35ae30aee..59ba1ff74 100644 --- a/mathjax3-ts/output/chtml/Wrapper.ts +++ b/mathjax3-ts/output/chtml/Wrapper.ts @@ -108,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 diff --git a/mathjax3-ts/output/chtml/Wrappers.ts b/mathjax3-ts/output/chtml/Wrappers.ts index 212bebafb..e0fb7ebf8 100644 --- a/mathjax3-ts/output/chtml/Wrappers.ts +++ b/mathjax3-ts/output/chtml/Wrappers.ts @@ -31,6 +31,7 @@ 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'; @@ -51,6 +52,9 @@ export const CHTMLWrappers: {[kind: string]: typeof CHTMLWrapper} = { [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/munderover.ts b/mathjax3-ts/output/chtml/Wrappers/munderover.ts new file mode 100644 index 000000000..381d198c2 --- /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.base.toCHTML(base); + this.script.toCHTML(under); + const basebox = this.base.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.base.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.base.toCHTML(base); + const overbox = this.script.getBBox(); + const basebox = this.base.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.base.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 under() { + return this.childNodes[(this.node as MmlMunderover).under]; + } + + /* + * @return{CHTMLWrapper) The wrapped overder node + */ + public get over() { + return this.childNodes[(this.node as MmlMunderover).over]; + } + + /* + * Needed for movablelimits + * + * @override + */ + public get sub() { + return this.under; + } + + /* + * Needed for movablelimits + * + * @override + */ + public get sup() { + return this.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 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.over.toCHTML(over); + this.base.toCHTML(base); + this.under.toCHTML(under); + const overbox = this.over.getBBox(); + const basebox = this.base.getBBox(); + const underbox = this.under.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.over.getBBox(); + const basebox = this.base.getBBox(); + const underbox = this.under.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 index bae7a7a17..e3d9d2299 100644 --- a/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts +++ b/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts @@ -82,6 +82,24 @@ export class CHTMLscriptbase extends CHTMLWrapper { 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.base; + 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) * @@ -129,17 +147,68 @@ export class CHTMLscriptbase extends CHTMLWrapper { ); } + /***************************************************************************/ /* - * @return{boolean} True if the base is an mi, mn, or mo (not a largeop) consisting of a single character + * Methods for under-over nodes */ - protected isCharBase() { - let base = this.base; - 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')); + + /* + * @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.base.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 undercripts (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)) + '"'; From 3319e718f25bfcc0908d0ea67e19f789e9cce582 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sat, 28 Oct 2017 10:39:08 -0400 Subject: [PATCH 4/6] Fixes requested by Volker --- mathjax3-ts/output/chtml/Wrappers/mfrac.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathjax3-ts/output/chtml/Wrappers/mfrac.ts b/mathjax3-ts/output/chtml/Wrappers/mfrac.ts index ccdfff66e..e4fef84cc 100644 --- a/mathjax3-ts/output/chtml/Wrappers/mfrac.ts +++ b/mathjax3-ts/output/chtml/Wrappers/mfrac.ts @@ -147,7 +147,7 @@ export class CHTMLmfrac extends CHTMLWrapper { const dbox = this.childNodes[1].getBBox(); const pad = (this.node.getProperty('withDelims') as boolean ? 0 : this.font.params.nulldelimiterspace); const a = this.font.params.axis_height; - const T = (display ? 3.5 : 1.5) * this.font.params.rule_thickness;; + 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; From 1a6e668bd71a3430075d20466cee39d1d223386b Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sat, 28 Oct 2017 17:46:21 -0400 Subject: [PATCH 5/6] Changes requested by Volker. --- mathjax3-ts/output/chtml/Wrappers/msubsup.ts | 18 +++++++++--------- .../output/chtml/Wrappers/scriptbase.ts | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mathjax3-ts/output/chtml/Wrappers/msubsup.ts b/mathjax3-ts/output/chtml/Wrappers/msubsup.ts index b05f3a91a..6b3d0797e 100644 --- a/mathjax3-ts/output/chtml/Wrappers/msubsup.ts +++ b/mathjax3-ts/output/chtml/Wrappers/msubsup.ts @@ -108,14 +108,14 @@ export class CHTMLmsubsup extends CHTMLscriptbase { /* * @return{CHTMLWrapper} The wrapper for the subscript */ - public get sub() { + public get subChild() { return this.childNodes[(this.node as MmlMsubsup).sub]; } /* * @return{CHTMLWrapper} The wrapper for the superscript */ - public get sup() { + public get supChild() { return this.childNodes[(this.node as MmlMsubsup).sup]; } @@ -124,22 +124,22 @@ export class CHTMLmsubsup extends CHTMLscriptbase { */ public toCHTML(parent: HTMLElement) { this.chtml = this.standardCHTMLnode(parent); - const [u, v, q] = this.getUVQ(this.base.getBBox(), this.sub.getBBox(), this.sup.getBBox()); + const [u, v, q] = this.getUVQ(this.baseChild.getBBox(), this.subChild.getBBox(), this.supChild.getBBox()); const style = {'vertical-align': this.em(v)}; - this.base.toCHTML(this.chtml); + this.baseChild.toCHTML(this.chtml); const stack = this.chtml.appendChild(this.html('mjx-script', {style})); - this.sup.toCHTML(stack); + this.supChild.toCHTML(stack); stack.appendChild(this.html('mjx-spacer', {style: {'margin-top': this.em(q)}})); - this.sub.toCHTML(stack); + this.subChild.toCHTML(stack); } /* * @override */ public computeBBox(bbox: BBox) { - const basebox = this.base.getBBox(); - const subbox = this.sub.getBBox(); - const supbox = this.sup.getBBox(); + const basebox = this.baseChild.getBBox(); + const subbox = this.subChild.getBBox(); + const supbox = this.supChild.getBBox(); bbox.empty(); bbox.append(basebox); const w = bbox.w; diff --git a/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts b/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts index bae7a7a17..329534c6c 100644 --- a/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts +++ b/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts @@ -42,7 +42,7 @@ export class CHTMLscriptbase extends CHTMLWrapper { /* * @return{CHTMLWrapper} The base element's wrapper */ - public get base() { + public get baseChild() { return this.childNodes[(this.node as MmlMsubsup).base]; } @@ -61,9 +61,9 @@ export class CHTMLscriptbase extends CHTMLWrapper { */ public toCHTML(parent: HTMLElement) { this.chtml = this.standardCHTMLnode(parent); - const v = this.getOffset(this.base.getBBox(), this.script.getBBox()); + const v = this.getOffset(this.baseChild.getBBox(), this.script.getBBox()); const style = {'vertical-align': this.em(v)}; - this.base.toCHTML(this.chtml); + this.baseChild.toCHTML(this.chtml); this.script.toCHTML(this.chtml.appendChild(this.html('mjx-script', {style}))); } @@ -74,7 +74,7 @@ export class CHTMLscriptbase extends CHTMLWrapper { * @override */ public computeBBox(bbox: BBox) { - const basebox = this.base.getBBox(); + const basebox = this.baseChild.getBBox(); const scriptbox = this.script.getBBox(); bbox.append(basebox); bbox.combine(scriptbox, bbox.w, this.getOffset(basebox, scriptbox)); @@ -133,7 +133,7 @@ export class CHTMLscriptbase extends CHTMLWrapper { * @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.base; + let base = this.baseChild; if ((base.node.isKind('mstyle') || base.node.isKind('mrow')) && base.childNodes.length === 1) { base = base.childNodes[0]; } From 9a594b65f0c2153f84ea7991e281d93259828fdd Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sat, 28 Oct 2017 18:03:19 -0400 Subject: [PATCH 6/6] Fix typo in comment. --- mathjax3-ts/output/chtml/Wrappers/scriptbase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts b/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts index 856faade7..de3b62783 100644 --- a/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts +++ b/mathjax3-ts/output/chtml/Wrappers/scriptbase.ts @@ -177,7 +177,7 @@ export class CHTMLscriptbase extends CHTMLWrapper { } /* - * Get the separation and offset for undercripts (TeXBoox Appendix G 13, 13a) + * 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