diff --git a/ts/output/chtml/Wrappers/mmultiscripts.ts b/ts/output/chtml/Wrappers/mmultiscripts.ts index 5cd16c044..b4f3d17b7 100644 --- a/ts/output/chtml/Wrappers/mmultiscripts.ts +++ b/ts/output/chtml/Wrappers/mmultiscripts.ts @@ -69,14 +69,14 @@ CommonMmultiscriptsMixin, Constructor, Constructor, Constructor, Constructor, CHTMLConstructor extends CommonMsubsup combinePrePost(pre: BBox, post: BBox): BBox; /** - * @return {ScriptData} The bounding box information about all the scripts + * Compute the bounding box information about all the scripts */ - getScriptData(): ScriptData; + getScriptData(): void; /** * @return {ScriptLists} The bounding boxes for all the scripts divided into lists by position @@ -158,6 +158,14 @@ export function CommonMmultiscriptsMixin< */ public firstPrescript = 0; + /** + * @override + */ + constructor(...args: any[]) { + super(...args); + this.getScriptData(); + } + /*************************************************************/ /** @@ -182,10 +190,10 @@ export function CommonMmultiscriptsMixin< // to get a common offset for both // const scriptspace = this.font.params.scriptspace; - const data = this.getScriptData(); + const data = this.scriptData; const sub = this.combinePrePost(data.sub, data.psub); const sup = this.combinePrePost(data.sup, data.psup); - const [u, v] = this.getUVQ(data.base, sub, sup); + const [u, v] = this.getUVQ(sub, sup); // // Lay out the pre-scripts, then the base, then the post-scripts // @@ -206,15 +214,9 @@ export function CommonMmultiscriptsMixin< } /** - * @return {ScriptData} The bounding box information about all the scripts + * Compute the bounding box information about all the scripts */ - public getScriptData(): ScriptData { - // - // Return cached data, if any - // - if (this.scriptData) { - return this.scriptData; - } + public getScriptData() { // // Initialize the bounding box data // @@ -228,13 +230,12 @@ export function CommonMmultiscriptsMixin< const lists = this.getScriptBBoxLists(); this.combineBBoxLists(data.sub, data.sup, lists.subList, lists.supList); this.combineBBoxLists(data.psub, data.psup, lists.psubList, lists.psupList); - this.scriptData.base = lists.base[0]; + data.base = lists.base[0]; // // Save the lengths and return the data // - this.scriptData.numPrescripts = lists.psubList.length; - this.scriptData.numScripts = lists.subList.length; - return this.scriptData; + data.numPrescripts = lists.psubList.length; + data.numScripts = lists.subList.length; } /** @@ -314,24 +315,24 @@ export function CommonMmultiscriptsMixin< /** * @override */ - public getUVQ(basebox: BBox, subbox: BBox, supbox: BBox) { + public getUVQ(subbox: BBox, supbox: BBox) { if (!this.UVQ) { let [u, v, q] = [0, 0, 0]; if (subbox.h === 0 && subbox.d === 0) { // // Use placement for superscript only // - u = this.getU(basebox, supbox); + u = this.getU(); } else if (supbox.h === 0 && supbox.d === 0) { // // Use placement for subsccript only // - u = -this.getV(basebox, subbox); + u = -this.getV(); } else { // // Use placement for both // - [u, v, q] = super.getUVQ(basebox, subbox, supbox); + [u, v, q] = super.getUVQ(subbox, supbox); } this.UVQ = [u, v, q]; } diff --git a/ts/output/common/Wrappers/msubsup.ts b/ts/output/common/Wrappers/msubsup.ts index 11ae8bd27..389d7f87c 100644 --- a/ts/output/common/Wrappers/msubsup.ts +++ b/ts/output/common/Wrappers/msubsup.ts @@ -60,7 +60,7 @@ export function CommonMsubMixin< /** * @override */ - public get script() { + public get scriptChild() { return this.childNodes[(this.node as MmlMsub).sub]; } @@ -69,8 +69,8 @@ export function CommonMsubMixin< * * @override */ - public getOffset(bbox: BBox, sbox: BBox) { - return [0, -this.getV(bbox, sbox)]; + public getOffset() { + return [0, -this.getV()]; } }; @@ -103,14 +103,14 @@ export type MsupConstructor = Constructor>; export function CommonMsupMixin< W extends AnyWrapper, T extends ScriptbaseConstructor ->(Base: T): MsubConstructor & T { +>(Base: T): MsupConstructor & T { return class extends Base { /** * @override */ - public get script() { + public get scriptChild() { return this.childNodes[(this.node as MmlMsup).sup]; } @@ -119,9 +119,9 @@ export function CommonMsupMixin< * * @override */ - public getOffset(bbox: BBox, sbox: BBox) { + public getOffset() { const x = (this.baseCore.bbox.ic ? .05 * this.baseCore.bbox.ic + .05 : 0); - return [x * this.coreScale(), this.getU(bbox, sbox)]; + return [x * this.baseScale, this.getU()]; } }; @@ -155,12 +155,11 @@ export interface CommonMsubsup extends CommonScriptbase /** * 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 */ - getUVQ(basebox: BBox, subbox: BBox, supbox: BBox): number[]; + getUVQ(subbox?: BBox, supbox?: BBox): number[]; } /** @@ -209,15 +208,13 @@ export function CommonMsubsupMixin< */ public computeBBox(bbox: BBox, recompute: boolean = false) { const basebox = this.baseChild.getBBox(); - const subbox = this.subChild.getBBox(); - const supbox = this.supChild.getBBox(); + const [subbox, supbox] = [this.subChild.getBBox(), this.supChild.getBBox()]; bbox.empty(); bbox.append(basebox); const w = bbox.w; - const [u, v] = this.getUVQ(basebox, subbox, supbox); - const x = (this.baseCore.bbox.ic ? this.coreIC() * this.coreScale() : 0); + const [u, v] = this.getUVQ(); bbox.combine(subbox, w, v); - bbox.combine(supbox, w + x, u); + bbox.combine(supbox, w + this.baseIc, u); bbox.w += this.font.params.scriptspace; bbox.clean(); this.setChildPWidths(recompute); @@ -226,21 +223,25 @@ export function CommonMsubsupMixin< /** * 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 */ - public getUVQ(basebox: BBox, subbox: BBox, supbox: BBox): number[] { + public getUVQ( + subbox: BBox = this.subChild.getBBox(), + supbox: BBox = this.supChild.getBBox() + ): number[] { + const basebox = this.baseCore.getBBox(); if (this.UVQ) return this.UVQ; const tex = this.font.params; const t = 3 * tex.rule_thickness; const subscriptshift = this.length2em(this.node.attributes.get('subscriptshift'), tex.sub2); - const drop = (this.isCharBase() ? 0 : basebox.d * basebox.rscale + tex.sub_drop * subbox.rscale); + const scale = this.baseScale; + const drop = (this.baseIsChar && scale === 1 ? 0 : basebox.d * scale + tex.sub_drop * subbox.rscale); // // u and v are the veritcal shifts of the scripts, initially set to minimum values and then adjusted // - let [u, v] = [this.getU(basebox, supbox), Math.max(drop, subscriptshift)]; + let [u, v] = [this.getU(), Math.max(drop, subscriptshift)]; // // q is the space currently between the super- and subscripts. // If it is less than 3 rule thicknesses, diff --git a/ts/output/common/Wrappers/munderover.ts b/ts/output/common/Wrappers/munderover.ts index 1f977aead..4af53e3c3 100644 --- a/ts/output/common/Wrappers/munderover.ts +++ b/ts/output/common/Wrappers/munderover.ts @@ -60,7 +60,7 @@ export function CommonMunderMixin< /** * @override */ - public get script() { + public get scriptChild() { return this.childNodes[(this.node as MmlMunder).under]; } @@ -83,7 +83,7 @@ export function CommonMunderMixin< } bbox.empty(); const basebox = this.baseChild.getBBox(); - const underbox = this.script.getBBox(); + const underbox = this.scriptChild.getBBox(); const v = this.getUnderKV(basebox, underbox)[1]; const delta = this.getDelta(true); const [bw, uw] = this.getDeltaW([basebox, underbox], [0, -delta]); @@ -132,7 +132,7 @@ export function CommonMoverMixin< /** * @override */ - public get script() { + public get scriptChild() { return this.childNodes[(this.node as MmlMover).over]; } @@ -155,7 +155,7 @@ export function CommonMoverMixin< } bbox.empty(); const basebox = this.baseChild.getBBox(); - const overbox = this.script.getBBox(); + const overbox = this.scriptChild.getBBox(); const u = this.getOverKU(basebox, overbox)[1]; const delta = this.getDelta(); const [bw, ow] = this.getDeltaW([basebox, overbox], [0, delta]); diff --git a/ts/output/common/Wrappers/scriptbase.ts b/ts/output/common/Wrappers/scriptbase.ts index 59f2f1d9a..36cd522c8 100644 --- a/ts/output/common/Wrappers/scriptbase.ts +++ b/ts/output/common/Wrappers/scriptbase.ts @@ -46,7 +46,7 @@ export interface CommonScriptbase extends AnyWrapper { /** * The core mi or mo of the base (or the base itself if there isn't one) */ - baseCore: W; + readonly baseCore: W; /** * The base element's wrapper @@ -54,56 +54,90 @@ export interface CommonScriptbase extends AnyWrapper { readonly baseChild: W; /** - * The script element's wrapper (overridden in subclasses) + * The relative scaling of the base compared to the munderover/msubsup */ - readonly script: W; + readonly baseScale: number; /** - * @return {boolean} True if the base is an mi, mn, or mo (not a largeop) consisting of a single character + * The italic correction of the base (if any) */ - isCharBase(): boolean; + readonly baseIc: number; + + /** + * True if the base is a single character + */ + readonly baseIsChar: boolean; + + /** + * The script element's wrapper (overridden in subclasses) + */ + readonly scriptChild: W; /***************************************************************************/ /* - * Methods for sub-sup nodes + * Methods for information about the core element for the base */ /** - * @return {number} The ic for the core element + * @return {W} The wrapper for the base core mi or mo (or whatever) */ - coreIC(): number; + getBaseCore(): W; /** - * @return {number} The relative scaling of the base + * @return {W} The base fence item or null + */ + getSemanticBase(): W; + + /** + * Recursively retrieves an element for a given fencepointer. + * + * @param {W} fence The potential fence. + * @param {string} id The fencepointer id. + * @return {W} The original fence the scripts belong to. + */ + getBaseFence(fence: W, id: string): W; + + /** + * @return {number} The scaling factor for the base core relative to the munderover/msubsup + */ + getBaseScale(): number; + + /** + * The base's italic correction (properly scaled) + */ + getBaseIc(): number; + + /** + * @return {boolean} True if the base is an mi, mn, or mo (not a largeop) consisting of + * a single unstretched character + */ + isCharBase(): boolean; + + /***************************************************************************/ + /* + * Methods for sub-sup nodes */ - coreScale(): number; /** * 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 horizontal and vertical offsets for the script */ - getOffset(bbox: BBox, sbox: BBox): number[]; + getOffset(): number[]; /** * 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 */ - getV(bbox: BBox, sbox: BBox): number; + getV(): number; /** * 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 */ - getU(bbox: BBox, sbox: BBox): number; + getU(): number; /***************************************************************************/ /* @@ -193,6 +227,21 @@ export function CommonScriptbaseMixin< */ public baseCore: W; + /** + * The base element's wrapper + */ + public baseScale: number = 1; + + /** + * The relative scaling of the base compared to the munderover/msubsup + */ + public baseIc: number = 0; + + /** + * True if the base is a single character + */ + public baseIsChar: boolean = false; + /** * @return {W} The base element's wrapper */ @@ -203,7 +252,7 @@ export function CommonScriptbaseMixin< /** * @return {W} The script element's wrapper (overridden in subclasses) */ - public get script(): W { + public get scriptChild(): W { return this.childNodes[1]; } @@ -215,77 +264,119 @@ export function CommonScriptbaseMixin< // // Find the base core // - let core = this.baseCore = this.childNodes[0]; + const core = this.baseCore = this.getBaseCore(); if (!core) return; - while (core.childNodes.length === 1 && - (core.node.isKind('mrow') || core.node.isKind('TeXAtom') || - core.node.isKind('mstyle') || core.node.isKind('mpadded') || - core.node.isKind('mphantom') || core.node.isKind('semantics'))) { - core = core.childNodes[0]; - if (!core) return; - } - if (!('noIC' in core)) return; - this.baseCore = core; // // Check if the base is a mi or mo that needs italic correction removed // - if (!(this.constructor as CommonScriptbaseClass).useIC) { - (core as CommonMo).noIC = true; + if (('noIC' in core) && !(this.constructor as CommonScriptbaseClass).useIC) { + (core as unknown as CommonMo).noIC = true; + } + // + // Get information about the base element + // + this.baseScale = this.getBaseScale(); + this.baseIc = this.getBaseIc(); + this.baseIsChar = this.isCharBase(); + } + + /***************************************************************************/ + /* + * Methods for information about the core element for the base + */ + + /** + * @return {W} The wrapper for the base core mi or mo (or whatever) + */ + public getBaseCore(): W { + let core = this.getSemanticBase() || this.childNodes[0]; + while (core && (core.childNodes.length === 1 && + (core.node.isKind('mrow') || core.node.isKind('TeXAtom') || + core.node.isKind('mstyle') || core.node.isKind('mpadded') || + core.node.isKind('mphantom') || core.node.isKind('semantics')))) { + core = core.childNodes[0]; } + return core || this.childNodes[0]; } /** - * This gives the common bbox for msub and msup. It is overridden - * for all the others (msubsup, munder, mover, munderover). - * - * @override + * @return {W} The base fence item or null */ - public computeBBox(bbox: BBox, recompute: boolean = false) { - const basebox = this.baseChild.getBBox(); - const scriptbox = this.script.getBBox(); - const [x, y] = this.getOffset(basebox, scriptbox); - bbox.append(basebox); - bbox.combine(scriptbox, bbox.w + x, y); - bbox.w += this.font.params.scriptspace; - bbox.clean(); - this.setChildPWidths(recompute); + public getSemanticBase(): W { + let fence = this.node.attributes.getExplicit('data-semantic-fencepointer') as string; + return this.getBaseFence(this.baseChild, fence); } /** - * @return {number} The ic for the core element + * Recursively retrieves an element for a given fencepointer. + * + * @param {W} fence The potential fence. + * @param {string} id The fencepointer id. + * @return {W} The original fence the scripts belong to. */ - public coreIC(): number { - const corebox = this.baseCore.getBBox(); - return (corebox.ic ? 1.05 * corebox.ic + .05 : 0); + public getBaseFence(fence: W, id: string): W { + if (!fence || !fence.node.attributes || !id) { + return null; + } + if (fence.node.attributes.getExplicit('data-semantic-id') === id) { + return fence; + } + for (const child of fence.childNodes) { + const result = this.getBaseFence(child, id); + if (result) { + return result; + } + } + return null; } /** - * @return {number} The relative scaling of the base + * @return {number} The scaling factor for the base core relative to the munderover/msubsup */ - public coreScale(): number { - let scale = this.baseChild.getBBox().rscale; - let base = this.baseChild; - while ((base.node.isKind('mstyle') || base.node.isKind('mrow') || base.node.isKind('TeXAtom')) - && base.childNodes.length === 1) { - base = base.childNodes[0]; - scale *= base.getBBox().rscale; + public getBaseScale(): number { + let child = this.baseCore as any; + let scale = 1; + while (child && child !== this) { + const bbox = child.getBBox(); + scale *= bbox.rscale; + child = child.parent; } return scale; } + /** + * The base's italic correction (properly scaled) + */ + public getBaseIc(): number { + return (this.baseCore.bbox.ic ? 1.05 * this.baseCore.bbox.ic + .05 : 0) * this.baseScale; + } + /** * @return {boolean} True if the base is an mi, mn, or mo (not a largeop) consisting of a single character */ public isCharBase(): boolean { - let base = this.baseChild; - while ((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')) && + let base = this.baseCore; + return (((base.node.isKind('mo') && (base as any).size === null) || + base.node.isKind('mi') || base.node.isKind('mn')) && base.bbox.rscale === 1 && Array.from(base.getText()).length === 1 && !base.node.attributes.get('largeop')); } + /** + * This gives the common bbox for msub and msup. It is overridden + * for all the others (msubsup, munder, mover, munderover). + * + * @override + */ + public computeBBox(bbox: BBox, recompute: boolean = false) { + const [x, y] = this.getOffset(); + bbox.append(this.baseChild.getBBox()); + bbox.combine(this.scriptChild.getBBox(), bbox.w + x, y); + bbox.w += this.font.params.scriptspace; + bbox.clean(); + this.setChildPWidths(recompute); + } + /***************************************************************************/ /* * Methods for sub-sup nodes @@ -294,26 +385,25 @@ export function CommonScriptbaseMixin< /** * 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, number]} The horizontal and vertical offsets for the script */ - public getOffset(_bbox: BBox, _sbox: BBox): [number, number] { + public getOffset(): [number, number] { return [0, 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 */ - public getV(bbox: BBox, sbox: BBox): number { + public getV(): number { + const bbox = this.baseCore.getBBox(); + const sbox = this.scriptChild.getBBox(); const tex = this.font.params; const subscriptshift = this.length2em(this.node.attributes.get('subscriptshift'), tex.sub1); + const scale = this.baseScale; return Math.max( - this.isCharBase() ? 0 : bbox.d * bbox.rscale + tex.sub_drop * sbox.rscale, + this.baseIsChar && scale === 1 ? 0 : bbox.d * scale + tex.sub_drop * sbox.rscale, subscriptshift, sbox.h * sbox.rscale - (4 / 5) * tex.x_height ); @@ -322,18 +412,19 @@ export function CommonScriptbaseMixin< /** * 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 */ - public getU(bbox: BBox, sbox: BBox): number { + public getU(): number { + const bbox = this.baseCore.getBBox(); + const sbox = this.scriptChild.getBBox(); const tex = this.font.params; const attr = this.node.attributes.getList('displaystyle', 'superscriptshift'); const prime = this.node.getProperty('texprimestyle'); const p = prime ? tex.sup3 : (attr.displaystyle ? tex.sup1 : tex.sup2); const superscriptshift = this.length2em(attr.superscriptshift, p); + const scale = this.baseScale; return Math.max( - this.isCharBase() ? 0 : bbox.h * bbox.rscale - tex.sup_drop * sbox.rscale, + this.baseIsChar && scale === 1 ? 0 : bbox.h * scale - tex.sup_drop * sbox.rscale, superscriptshift, sbox.d * sbox.rscale + (1 / 4) * tex.x_height ); @@ -419,8 +510,8 @@ export function CommonScriptbaseMixin< */ public getDelta(noskew: boolean = false): number { const accent = this.node.attributes.get('accent'); - const ddelta = (accent && !noskew ? this.baseChild.coreMO().bbox.sk : 0); - return (DELTA * this.baseCore.bbox.ic / 2 + ddelta) * this.coreScale(); + const ddelta = (accent && !noskew ? this.baseCore.bbox.sk : 0); + return (DELTA * this.baseCore.bbox.ic / 2 + ddelta) * this.baseScale; } /** diff --git a/ts/output/svg/Wrappers/mmultiscripts.ts b/ts/output/svg/Wrappers/mmultiscripts.ts index 581f58915..147bcb4ad 100644 --- a/ts/output/svg/Wrappers/mmultiscripts.ts +++ b/ts/output/svg/Wrappers/mmultiscripts.ts @@ -48,14 +48,14 @@ CommonMmultiscriptsMixin, Constructor, Constructor, Constructor> } const svg = this.standardSVGnode(parent); - const [base, script] = [this.baseChild, this.script]; + const [base, script] = [this.baseChild, this.scriptChild]; const [bbox, sbox] = [base.getBBox(), script.getBBox()]; base.toSVG(svg); @@ -108,7 +108,7 @@ CommonMoverMixin, Constructor>> return; } const svg = this.standardSVGnode(parent); - const [base, script] = [this.baseChild, this.script]; + const [base, script] = [this.baseChild, this.scriptChild]; const [bbox, sbox] = [base.getBBox(), script.getBBox()]; base.toSVG(svg); diff --git a/ts/output/svg/Wrappers/scriptbase.ts b/ts/output/svg/Wrappers/scriptbase.ts index a6e187fab..7db7eeedc 100644 --- a/ts/output/svg/Wrappers/scriptbase.ts +++ b/ts/output/svg/Wrappers/scriptbase.ts @@ -59,11 +59,10 @@ CommonScriptbaseMixin, SVGConstructor>( public toSVG(parent: N) { const svg = this.standardSVGnode(parent); const bbox = this.baseChild.getBBox(); - const sbox = this.script.getBBox(); - const [x, v] = this.getOffset(bbox, sbox); + const [x, v] = this.getOffset(); this.baseChild.toSVG(svg); - this.script.toSVG(svg); - this.script.place(bbox.w * bbox.rscale + x, v); + this.scriptChild.toSVG(svg); + this.scriptChild.place(bbox.w * bbox.rscale + x, v); } }