1
0

add initial marp implementation with sample content and build configuration

This commit is contained in:
2025-09-13 18:13:22 +02:00
parent dcacc9b409
commit e5f219507f
10319 changed files with 1402023 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonTeXAtom wrapper mixin for the MmlTeXAtom object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {BBox} from '../../../util/BBox.js';
import {TEXCLASS} from '../../../core/MmlTree/MmlNode.js';
/*****************************************************************/
/**
* The CommonTeXAtom interface
*/
export interface CommonTeXAtom extends AnyWrapper {
}
/**
* Shorthand for the CommonTeXAtom constructor
*/
export type TeXAtomConstructor = Constructor<CommonTeXAtom>;
/*****************************************************************/
/**
* The CommonTeXAtom wrapper mixin for the TeXAtom object
*
* @template T The Wrapper class constructor type
*/
export function CommonTeXAtomMixin<T extends WrapperConstructor>(Base: T): TeXAtomConstructor & T {
return class extends Base {
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
super.computeBBox(bbox, recompute);
if (this.childNodes[0] && this.childNodes[0].bbox.ic) {
bbox.ic = this.childNodes[0].bbox.ic;
}
//
// Center VCENTER atoms vertically
//
if (this.node.texClass === TEXCLASS.VCENTER) {
const {h, d} = bbox;
const a = this.font.params.axis_height;
const dh = ((h + d) / 2 + a) - h; // new height minus old height
bbox.h += dh;
bbox.d -= dh;
}
}
};
}

View File

@@ -0,0 +1,143 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonTextNode wrapper mixin for the TextNode object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {BBox} from '../../../util/BBox.js';
import {TextNode} from '../../../core/MmlTree/MmlNode.js';
/*****************************************************************/
/**
* The CommonTextNode interface
*/
export interface CommonTextNode extends AnyWrapper {
/**
* @param {string} text The text to remap
* @param {string} variant The variant for the character
* @return {number[]} The unicode points for the (remapped) text
*/
remappedText(text: string, variant: string): number[];
}
/**
* Shorthand for the CommonTextNode constructor
*/
export type TextNodeConstructor = Constructor<CommonTextNode>;
/*****************************************************************/
/**
* The CommonTextNode wrapper mixin for the TextNode object
*
* @template T The Wrapper class constructor type
*/
export function CommonTextNodeMixin<T extends WrapperConstructor>(Base: T): TextNodeConstructor & T {
return class extends Base {
/**
* @override
*/
public computeBBox(bbox: BBox, _recompute: boolean = false) {
const variant = this.parent.variant;
const text = (this.node as TextNode).getText();
if (variant === '-explicitFont') {
//
// Measure the size of the text (using the DOM if possible)
//
const font = this.jax.getFontData(this.parent.styles);
const {w, h, d} = this.jax.measureText(text, variant, font);
bbox.h = h;
bbox.d = d;
bbox.w = w;
} else {
const chars = this.remappedText(text, variant);
bbox.empty();
//
// Loop through the characters and add them in one by one
//
for (const char of chars) {
let [h, d, w, data] = this.getVariantChar(variant, char);
if (data.unknown) {
//
// Measure unknown characters using the DOM (if possible)
//
const cbox = this.jax.measureText(String.fromCodePoint(char), variant);
w = cbox.w;
h = cbox.h;
d = cbox.d;
}
//
// Update the bounding box
//
bbox.w += w;
if (h > bbox.h) bbox.h = h;
if (d > bbox.d) bbox.d = d;
bbox.ic = data.ic || 0;
bbox.sk = data.sk || 0;
bbox.dx = data.dx || 0;
}
if (chars.length > 1) {
bbox.sk = 0;
}
bbox.clean();
}
}
/**
* @param {string} text The text to remap
* @param {string} variant The variant for the character
* @return {number[]} The unicode points for the (remapped) text
*/
public remappedText(text: string, variant: string): number[] {
const c = this.parent.stretch.c;
return (c ? [c] : this.parent.remapChars(this.unicodeChars(text, variant)));
}
/******************************************************/
/*
* TextNodes don't need these, since these properties
* are inherited from the parent nodes
*/
/**
* @override
*/
public getStyles() {}
/**
* @override
*/
public getVariant() {}
/**
* @override
*/
public getScale() {}
/**
* @override
*/
public getSpace() {}
};
}

View File

@@ -0,0 +1,193 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 CommonMaction wrapper mixin for the MmlMaction object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor, AnyWrapperClass} from '../Wrapper.js';
import {MmlMaction} from '../../../core/MmlTree/MmlNodes/maction.js';
import {BBox} from '../../../util/BBox.js';
import {split} from '../../../util/string.js';
/*****************************************************************/
/**
* The types needed to define the actiontypes
*
* @template W The maction wrapper type
*/
export type ActionData = {[name: string]: any};
export type ActionHandler<W extends AnyWrapper> = (node: W, data?: ActionData) => void;
export type ActionPair<W extends AnyWrapper> = [ActionHandler<W>, ActionData];
export type ActionMap<W extends AnyWrapper> = Map<string, ActionPair<W>>;
export type ActionDef<W extends AnyWrapper> = [string, [ActionHandler<W>, ActionData]];
export type EventHandler = (event: Event) => void;
/**
* Data used for tooltip actions
*/
export const TooltipData = {
dx: '.2em', // x-offset of tooltip from right side of maction bbox
dy: '.1em', // y-offset of tooltip from bottom of maction bbox
postDelay: 600, // milliseconds before tooltip posts
clearDelay: 100, // milliseconds before tooltip is removed
hoverTimer: new Map<any, number>(), // timers for posting tooltips
clearTimer: new Map<any, number>(), // timers for removing tooltips
/*
* clear the timers if any are active
*/
stopTimers: (node: any, data: ActionData) => {
if (data.clearTimer.has(node)) {
clearTimeout(data.clearTimer.get(node));
data.clearTimer.delete(node);
}
if (data.hoverTimer.has(node)) {
clearTimeout(data.hoverTimer.get(node));
data.hoverTimer.delete(node);
}
}
};
/*****************************************************************/
/**
* The CommonMaction interface
*
* @template W The maction wrapper type
*/
export interface CommonMaction<W extends AnyWrapper> extends AnyWrapper {
/**
* The handler for the specified actiontype
*/
action: ActionHandler<W>;
data: ActionData;
/**
* Tooltip offsets
*/
dx: number;
dy: number;
/**
* The selected child wrapper
*/
readonly selected: W;
}
/**
* The CommonMaction class interface
*
* @template W The maction wrapper type
*/
export interface CommonMactionClass<W extends AnyWrapper> extends AnyWrapperClass {
/**
* The valid action types and their handlers
*/
actions: ActionMap<W>;
}
/**
* Shorthand for the CommonMaction constructor
*
* @template W The maction wrapper type
*/
export type MactionConstructor<W extends AnyWrapper> = Constructor<CommonMaction<W>>;
/*****************************************************************/
/**
* The CommonMaction wrapper mixin for the MmlMaction object
*
* @template W The maction wrapper type
* @template T The Wrapper class constructor type
*/
export function CommonMactionMixin<
W extends AnyWrapper,
T extends WrapperConstructor
>(Base: T): MactionConstructor<W> & T {
return class extends Base {
/**
* The handler for the specified actiontype
*/
public action: ActionHandler<W>;
/**
* The data for the specified actiontype
*/
public data: ActionData;
/**
* The x-offset for tooltips
*/
public dx: number;
/**
* The y-offset for tooltips
*/
public dy: number;
/**
* @return {W} The selected child wrapper
*/
public get selected(): W {
const selection = this.node.attributes.get('selection') as number;
const i = Math.max(1, Math.min(this.childNodes.length, selection)) - 1;
return this.childNodes[i] || this.wrap((this.node as MmlMaction).selected);
}
/*************************************************************/
/**
* @override
*/
constructor(...args: any[]) {
super(...args);
const actions = (this.constructor as CommonMactionClass<W>).actions;
const action = this.node.attributes.get('actiontype') as string;
const [handler, data] = actions.get(action) || [((_node, _data) => {}) as ActionHandler<W>, {}];
this.action = handler;
this.data = data;
this.getParameters();
}
/**
* Look up attribute parameters
*/
public getParameters() {
const offsets = this.node.attributes.get('data-offsets') as string;
let [dx, dy] = split(offsets || '');
this.dx = this.length2em(dx || TooltipData.dx);
this.dy = this.length2em(dy || TooltipData.dy);
}
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
bbox.updateFrom(this.selected.getOuterBBox());
this.selected.setChildPWidths(recompute);
}
};
}

View File

@@ -0,0 +1,57 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 CommonMath wrapper mixin for the MmlMath object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
/*****************************************************************/
/**
* The CommonMath interface
*/
export interface CommonMath extends AnyWrapper {
}
/**
* Shorthand for the CommonMath constructor
*/
export type MathConstructor = Constructor<CommonMath>;
/*****************************************************************/
/**
* The CommonMath wrapper mixin for the MmlMath object
*
* @template T The Wrapper class constructor type
*/
export function CommonMathMixin<T extends WrapperConstructor>(Base: T): MathConstructor & T {
return class extends Base {
/**
* @override
*/
public getWrapWidth(_i: number) {
return (this.parent ? this.getBBox().w : this.metrics.containerWidth / this.jax.pxPerEm);
}
};
}

View File

@@ -0,0 +1,465 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 CommonMenclose wrapper mixin for the MmlMenclose object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor, AnyWrapperClass} from '../Wrapper.js';
import * as Notation from '../Notation.js';
import {CommonMsqrt} from './msqrt.js';
import {BBox} from '../../../util/BBox.js';
import {AbstractMmlNode} from '../../../core/MmlTree/MmlNode.js';
import {split} from '../../../util/string.js';
/*****************************************************************/
/**
* The CommonMenclose interface
*
* @template W The menclose wrapper type
*/
export interface CommonMenclose<W extends AnyWrapper, S extends CommonMsqrt, N> extends AnyWrapper {
/**
* The notations active on this menclose, and the one to use for the child, if any
*/
notations: Notation.List<W, N>;
renderChild: Notation.Renderer<W, N>;
/**
* fake msqrt for radial notation (if used)
*/
msqrt: S;
/**
* The padding, thickness, and shape of the arrow head
* (may be overridden using data-padding, data-thickness, and data-arrowhead attibutes)
*/
padding: number;
thickness: number;
arrowhead: {x: number, y: number, dx: number};
/**
* The top, right, bottom, and left padding, added by notations
*/
TRBL: Notation.PaddingData;
/**
* Look up the data-* attributes and override the default values
*/
getParameters(): void;
/**
* Get the notations given in the notation attribute
* and check if any are used to render the child nodes
*/
getNotations(): void;
/**
* Remove any redundant notations
*/
removeRedundantNotations(): void;
/**
* Run any initialization needed by notations in use
*/
initializeNotations(): void;
/**
* @return {Notation.PaddingData} Array of the maximum extra space from the notations along each side
*/
getBBoxExtenders(): Notation.PaddingData;
/**
* @return {Notation.PaddingData} Array of padding (i.e., BBox minus border) along each side
*/
getPadding(): Notation.PaddingData;
/**
* Each entry in X gets replaced by the corresponding one in Y if it is larger
*
* @param {Notation.PaddingData} X An array of numbers
* @param {Notation.PaddingData} Y An array of numbers that replace smaller ones in X
*/
maximizeEntries(X: Notation.PaddingData, Y: Notation.PaddingData): void;
/**
* Get the offset amount for the given direction for vertical and horizontal centering
*
* @param {string} direction The direction 'X' or 'Y' for the offset
* @return {number} The amount of offset in that direction
*/
getOffset(direction: string): number;
/**
* @param {number} w The width of the box whose diagonal is needed
* @param {number} h The height of the box whose diagonal is needed
* @return {number[]} The angle and width of the diagonal of the box
*/
getArgMod(w: number, h: number): [number, number];
/**
* Create an arrow for output
*
* @param {number} w The length of the arrow
* @param {number} a The angle for the arrow
* @param {boolean} double True if this is a double-headed arrow
* @param {string} offset 'X' for vertical arrow, 'Y' for horizontal
* @param {number} trans Distance to translate in the offset direction
* @return {N} The newly created arrow
*/
arrow(w: number, a: number, double: boolean, offset?: string, trans?: number): N;
/**
* Get the angle and width of a diagonal arrow, plus the x and y extension
* past the content bounding box
*/
arrowData(): {a: number, W: number, x: number, y: number};
/**
* Get the angle and width for a diagonal arrow
*
* @return {[number, number]} The angle and width
*/
arrowAW(): [number, number];
/**
* Create an unattached msqrt wrapper to render the 'radical' notation.
* We replace the inferred mrow of the msqrt with the one from the menclose
* but without changing the parent pointer, so as not to detach it from
* the menclose (which would desrtoy the original MathML tree).
*
* @param {W} child The inferred mrow that is the child of this menclose
* @return {S} The newly created (but detached) msqrt wrapper
*/
createMsqrt(child: W): S;
/**
* @return {number[]} The differences between the msqrt bounding box
* and its child bounding box (i.e., the extra space
* created by the radical symbol).
*/
sqrtTRBL(): number[];
}
/**
* The CommonMenclose class interface
*
* @template W The menclose wrapper type
* @templare N The DOM node class
*/
export interface CommonMencloseClass<W extends AnyWrapper, N> extends AnyWrapperClass {
/**
* The definitions of the various notations
*/
notations: Notation.DefList<W, N>;
}
/**
* Shorthand for the CommonMenclose constructor
*
* @template W The menclose wrapper type
*/
export type MencloseConstructor<W extends AnyWrapper, S extends CommonMsqrt, N> = Constructor<CommonMenclose<W, S, N>>;
/*****************************************************************/
/**
* The CommonMenclose wrapper mixin for the MmlMenclose object
*
* @template W The menclose wrapper type
* @templare N The DOM node class
* @templare S The msqrt wrapper class
* @template T The Wrapper class constructor type
*/
export function CommonMencloseMixin<
W extends AnyWrapper,
S extends CommonMsqrt,
N,
T extends WrapperConstructor
>(Base: T): MencloseConstructor<W, S, N> & T {
return class extends Base {
/**
* The notations active on this menclose, if any
*/
public notations: Notation.List<W, N> = {};
/**
* The notation to use for the child, if any
*/
public renderChild: Notation.Renderer<W, N> = null;
/**
* fake msqrt for radial notation (if used)
*/
public msqrt: S = null;
/**
* The padding of the arrow head (may be overridden using data-padding attibute)
*/
public padding: number = Notation.PADDING;
/**
* The thickness of the arrow head (may be overridden using data-thickness attibute)
*/
public thickness: number = Notation.THICKNESS;
/**
* The shape of the arrow head (may be overridden using data-arrowhead attibutes)
*/
public arrowhead = {x: Notation.ARROWX, y: Notation.ARROWY, dx: Notation.ARROWDX};
/**
* The top, right, bottom, and left padding (added by notations)
*/
public TRBL: Notation.PaddingData = [0, 0, 0, 0];
/**
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
this.getParameters();
this.getNotations();
this.removeRedundantNotations();
this.initializeNotations();
this.TRBL = this.getBBoxExtenders();
}
/**
* Look up the data-* attributes and override the default values
*/
public getParameters() {
const attributes = this.node.attributes;
const padding = attributes.get('data-padding');
if (padding !== undefined) {
this.padding = this.length2em(padding, Notation.PADDING);
}
const thickness = attributes.get('data-thickness');
if (thickness !== undefined) {
this.thickness = this.length2em(thickness, Notation.THICKNESS);
}
const arrowhead = attributes.get('data-arrowhead') as string;
if (arrowhead !== undefined) {
let [x, y, dx] = split(arrowhead);
this.arrowhead = {
x: (x ? parseFloat(x) : Notation.ARROWX),
y: (y ? parseFloat(y) : Notation.ARROWY),
dx: (dx ? parseFloat(dx) : Notation.ARROWDX)
};
}
}
/**
* Get the notations given in the notation attribute
* and check if any are used to render the child nodes
*/
public getNotations() {
const Notations = (this.constructor as CommonMencloseClass<W, N>).notations;
for (const name of split(this.node.attributes.get('notation') as string)) {
const notation = Notations.get(name);
if (notation) {
this.notations[name] = notation;
if (notation.renderChild) {
this.renderChild = notation.renderer;
}
}
}
}
/**
* Remove any redundant notations
*/
public removeRedundantNotations() {
for (const name of Object.keys(this.notations)) {
if (this.notations[name]) {
const remove = this.notations[name].remove || '';
for (const notation of remove.split(/ /)) {
delete this.notations[notation];
}
}
}
}
/**
* Run any initialization needed by notations in use
*/
public initializeNotations() {
for (const name of Object.keys(this.notations)) {
const init = this.notations[name].init;
init && init(this as any);
}
}
/********************************************************/
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
//
// Combine the BBox from the child and add the extenders
//
let [T, R, B, L] = this.TRBL;
const child = this.childNodes[0].getBBox();
bbox.combine(child, L, 0);
bbox.h += T;
bbox.d += B;
bbox.w += R;
this.setChildPWidths(recompute);
}
/**
* @return {Notation.PaddingData} Array of the maximum extra space from the notations along each side
*/
public getBBoxExtenders(): Notation.PaddingData {
let TRBL = [0, 0, 0, 0] as Notation.PaddingData;
for (const name of Object.keys(this.notations)) {
this.maximizeEntries(TRBL, this.notations[name].bbox(this as any));
}
return TRBL;
}
/**
* @return {Notation.PaddingData} Array of padding (i.e., BBox minus border) along each side
*/
public getPadding(): Notation.PaddingData {
let BTRBL = [0, 0, 0, 0] as Notation.PaddingData;
for (const name of Object.keys(this.notations)) {
const border = this.notations[name].border;
if (border) {
this.maximizeEntries(BTRBL, border(this as any));
}
}
return [0, 1, 2, 3].map(i => this.TRBL[i] - BTRBL[i]) as Notation.PaddingData;
}
/**
* Each entry in X gets replaced by the corresponding one in Y if it is larger
*
* @param {Notation.PaddingData} X An array of numbers
* @param {Notation.PaddingData} Y An array of numbers that replace smaller ones in X
*/
public maximizeEntries(X: Notation.PaddingData, Y: Notation.PaddingData) {
for (let i = 0; i < X.length; i++) {
if (X[i] < Y[i]) {
X[i] = Y[i];
}
}
}
/********************************************************/
/**
* Get the offset amount for the given direction for vertical and horizontal centering
*
* @param {string} direction The direction 'X' or 'Y' for the offset
* @return {number} The amount of offset in that direction
*/
public getOffset(direction: string): number {
let [T, R, B, L] = this.TRBL;
const d = (direction === 'X' ? R - L : B - T) / 2;
return (Math.abs(d) > .001 ? d : 0);
}
/**
* @param {number} w The width of the box whose diagonal is needed
* @param {number} h The height of the box whose diagonal is needed
* @return {number[]} The angle and width of the diagonal of the box
*/
public getArgMod(w: number, h: number): [number, number] {
return [Math.atan2(h, w), Math.sqrt(w * w + h * h)];
}
/**
* Create an arrow using an svg element
*
* @param {number} w The length of the arrow
* @param {number} a The angle for the arrow
* @param {boolean} double True if this is a double-headed arrow
* @param {string} offset 'X' for vertical arrow, 'Y' for horizontal
* @param {number} dist Distance to translate in the offset direction
* @return {N} The newly created arrow
*/
public arrow(_w: number, _a: number, _double: boolean, _offset: string = '', _dist: number = 0): N {
return null as N;
}
/**
* Get the angle and width of a diagonal arrow, plus the x and y extension
* past the content bounding box
*
* @return {Object} The angle, width, and x and y extentions
*/
public arrowData(): {a: number, W: number, x: number, y: number} {
const [p, t] = [this.padding, this.thickness];
const r = t * (this.arrowhead.x + Math.max(1, this.arrowhead.dx));
const {h, d, w} = this.childNodes[0].getBBox();
const H = h + d;
const R = Math.sqrt(H * H + w * w);
const x = Math.max(p, r * w / R);
const y = Math.max(p, r * H / R);
const [a, W] = this.getArgMod(w + 2 * x, H + 2 * y);
return {a, W, x, y};
}
/**
* Get the angle and width for a diagonal arrow
*
* @return {[number, number]} The angle and width
*/
public arrowAW(): [number, number] {
const {h, d, w} = this.childNodes[0].getBBox();
const [T, R, B, L] = this.TRBL;
return this.getArgMod(L + w + R, T + h + d + B);
}
/********************************************************/
/**
* Create an unattached msqrt wrapper to render the 'radical' notation.
* We replace the inferred mrow of the msqrt with the one from the menclose
* but without changing the parent pointer, so as not to detach it from
* the menclose (which would desrtoy the original MathML tree).
*
* @param {W} child The inferred mrow that is the child of this menclose
* @return {S} The newly created (but detached) msqrt wrapper
*/
public createMsqrt(child: W): S {
const mmlFactory = (this.node as AbstractMmlNode).factory;
const mml = mmlFactory.create('msqrt');
mml.inheritAttributesFrom(this.node);
mml.childNodes[0] = child.node;
const node = this.wrap(mml) as S;
node.parent = this;
return node;
}
/**
* @return {number[]} The differences between the msqrt bounding box
* and its child bounding box (i.e., the extra space
* created by the radical symbol).
*/
public sqrtTRBL(): [number, number, number, number] {
const bbox = this.msqrt.getBBox();
const cbox = this.msqrt.childNodes[0].getBBox();
return [bbox.h - cbox.h, 0, bbox.d - cbox.d, bbox.w - cbox.w];
}
};
}

View File

@@ -0,0 +1,144 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 CommonMfenced wrapper mixin for the MmlMfenced object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {CommonInferredMrow} from './mrow.js';
import {MmlNode, AbstractMmlNode} from '../../../core/MmlTree/MmlNode.js';
import {MmlMfenced} from '../../../core/MmlTree/MmlNodes/mfenced.js';
import {BBox} from '../../../util/BBox.js';
/*****************************************************************/
/**
* The CommonMfenced interface
*/
export interface CommonMfenced extends AnyWrapper {
/**
* An mrow to use for the layout of the mfenced
*/
mrow: CommonInferredMrow;
/**
* Creates the mrow wrapper to use for the layout
*/
createMrow(): void;
/**
* Populate the mrow with wrapped mo elements interleaved
* with the mfenced children (the mo's are already created
* in the mfenced object)
*/
addMrowChildren(): void;
/**
* Wrap an mo element and push it onto the mrow
*
* @param {MmlNode} node The mo element to push on the mrow
*/
addMo(node: MmlNode): void;
}
/**
* Shorthand for the CommonMfenced constructor
*/
export type MfencedConstructor = Constructor<CommonMfenced>;
/*****************************************************************/
/**
* The CommonMfenced wrapper mixin for the MmlMfenced object
*
* @template T The Wrapper class constructor type
*/
export function CommonMfencedMixin<T extends WrapperConstructor>(Base: T): MfencedConstructor & T {
return class extends Base {
/**
* An mrow to use for the layout of the mfenced
*/
public mrow: CommonInferredMrow = null;
/**
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
this.createMrow();
this.addMrowChildren();
}
/**
* Creates the mrow wrapper to use for the layout
*/
public createMrow() {
const mmlFactory = (this.node as AbstractMmlNode).factory;
const mrow = mmlFactory.create('inferredMrow');
mrow.inheritAttributesFrom(this.node);
this.mrow = this.wrap(mrow) as CommonInferredMrow;
this.mrow.parent = this;
}
/**
* Populate the mrow with wrapped mo elements interleaved
* with the mfenced children (the mo's are already created
* in the mfenced object)
*/
public addMrowChildren() {
const mfenced = this.node as MmlMfenced;
const mrow = this.mrow;
this.addMo(mfenced.open);
if (this.childNodes.length) {
mrow.childNodes.push(this.childNodes[0]);
}
let i = 0;
for (const child of this.childNodes.slice(1)) {
this.addMo(mfenced.separators[i++]);
mrow.childNodes.push(child);
}
this.addMo(mfenced.close);
mrow.stretchChildren();
}
/**
* Wrap an mo element and push it onto the mrow
*
* @param {MmlNode} node The mo element to push on the mrow
*/
public addMo(node: MmlNode) {
if (!node) return;
const mo = this.wrap(node);
this.mrow.childNodes.push(mo);
mo.parent = this.mrow;
}
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
bbox.updateFrom(this.mrow.getOuterBBox());
this.setChildPWidths(recompute);
}
};
}

View File

@@ -0,0 +1,302 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMfrac wrapper mixin for the MmlMfrac object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {CommonMo} from './mo.js';
import {BBox} from '../../../util/BBox.js';
import {DIRECTION} from '../FontData.js';
/*****************************************************************/
/**
* The CommonMfrac interface
*/
export interface CommonMfrac extends AnyWrapper {
/**
* @param {BBox} bbox The buonding box to modify
* @param {boolean} display True for display-mode fractions
* @param {number} t The thickness of the line
*/
getFractionBBox(bbox: BBox, display: boolean, t: number): void;
/**
* @param {boolean} display True for display-mode fractions
* @param {number} t The thickness of the line
* @return {Object} The expanded rule thickness (T), and baseline offsets
* for numerator and denomunator (u and v)
*/
getTUV(display: boolean, t: number): {T: number, u: number, v: number};
/**
* @param {BBox} bbox The bounding box to modify
* @param {boolean} display True for display-mode fractions
*/
getAtopBBox(bbox: BBox, display: boolean): void;
/**
* @param {boolean} display True for diplay-mode fractions
* @return {Object}
* The vertical offsets of the numerator (u), the denominator (v),
* the separation between the two, and the bboxes themselves.
*/
getUVQ(display: boolean): {u: number, v: number, q: number, nbox: BBox, dbox: BBox};
/**
* @param {BBox} bbox The boundng box to modify
* @param {boolean} display True for display-mode fractions
*/
getBevelledBBox(bbox: BBox, display: boolean): void;
/**
* @param {boolean} display True for display-style fractions
* @return {Object} The height (H) of the bevel, horizontal offest (delta)
* vertical offsets (u and v) of the parts, and
* bounding boxes of the parts.
*/
getBevelData(display: boolean): {H: number, delta: number, u: number, v: number, nbox: BBox, dbox: BBox};
/**
* @return {boolean} True if in display mode, false otherwise
*/
isDisplay(): boolean;
}
/**
* Shorthand for the CommonMfrac constructor
*/
export type MfracConstructor = Constructor<CommonMfrac>;
/*****************************************************************/
/**
* The CommonMfrac wrapper mixin for the MmlMfrac object
*
* @template T The Wrapper class constructor type
*/
export function CommonMfracMixin<T extends WrapperConstructor>(Base: T): MfracConstructor & T {
return class extends Base {
/**
* Wrapper for <mo> to use for bevelled fraction
*/
public bevel: CommonMo = null;
/**
* Padding around fractions
*/
public pad: number;
/************************************************/
/**
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
this.pad = (this.node.getProperty('withDelims') as boolean ? 0 : this.font.params.nulldelimiterspace);
//
// create internal bevel mo element
//
if (this.node.attributes.get('bevelled')) {
const {H} = this.getBevelData(this.isDisplay());
const bevel = this.bevel = this.createMo('/') as CommonMo;
bevel.node.attributes.set('symmetric', true);
bevel.canStretch(DIRECTION.Vertical);
bevel.getStretchedVariant([H], true);
}
}
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
bbox.empty();
const {linethickness, bevelled} = this.node.attributes.getList('linethickness', 'bevelled');
const display = this.isDisplay();
let w = null as (number | null);
if (bevelled) {
this.getBevelledBBox(bbox, display);
} else {
const thickness = this.length2em(String(linethickness), .06);
w = -2 * this.pad;
if (thickness === 0) {
this.getAtopBBox(bbox, display);
} else {
this.getFractionBBox(bbox, display, thickness);
w -= .2;
}
w += bbox.w;
}
bbox.clean();
this.setChildPWidths(recompute, w);
}
/************************************************/
/**
* @param {BBox} bbox The buonding box to modify
* @param {boolean} display True for display-mode fractions
* @param {number} t The thickness of the line
*/
public getFractionBBox(bbox: BBox, display: boolean, t: number) {
const nbox = this.childNodes[0].getOuterBBox();
const dbox = this.childNodes[1].getOuterBBox();
const tex = this.font.params;
const a = tex.axis_height;
const {T, u, v} = this.getTUV(display, t);
bbox.combine(nbox, 0, a + T + Math.max(nbox.d * nbox.rscale, u));
bbox.combine(dbox, 0, a - T - Math.max(dbox.h * dbox.rscale, v));
bbox.w += 2 * this.pad + .2;
}
/**
* @param {boolean} display True for display-mode fractions
* @param {number} t The thickness of the line
* @return {Object} The expanded rule thickness (T), and baseline offsets
* for numerator and denomunator (u and v)
*/
public getTUV(display: boolean, t: number): {T: number, u: number, v: number} {
const tex = this.font.params;
const a = tex.axis_height;
const T = (display ? 3.5 : 1.5) * t;
return {T: (display ? 3.5 : 1.5) * t,
u: (display ? tex.num1 : tex.num2) - a - T,
v: (display ? tex.denom1 : tex.denom2) + a - T};
}
/************************************************/
/**
* @param {BBox} bbox The bounding box to modify
* @param {boolean} display True for display-mode fractions
*/
public getAtopBBox(bbox: BBox, display: boolean) {
const {u, v, nbox, dbox} = this.getUVQ(display);
bbox.combine(nbox, 0, u);
bbox.combine(dbox, 0, -v);
bbox.w += 2 * this.pad;
}
/**
* @param {boolean} display True for diplay-mode fractions
* @return {Object}
* The vertical offsets of the numerator (u), the denominator (v),
* the separation between the two, and the bboxes themselves.
*/
public getUVQ(display: boolean): {u: number, v: number, q: number, nbox: BBox, dbox: BBox} {
const nbox = this.childNodes[0].getOuterBBox();
const dbox = this.childNodes[1].getOuterBBox();
const tex = this.font.params;
//
// Initial offsets (u, v)
// Minimum separation (p)
// Actual separation with initial positions (q)
//
let [u, v] = (display ? [tex.num1, tex.denom1] : [tex.num3, tex.denom2]);
let p = (display ? 7 : 3) * tex.rule_thickness;
let q = (u - nbox.d * nbox.scale) - (dbox.h * dbox.scale - v);
//
// If actual separation is less than minimum, move them farther apart
//
if (q < p) {
u += (p - q) / 2;
v += (p - q) / 2;
q = p;
}
return {u, v, q, nbox, dbox};
}
/************************************************/
/**
* @param {BBox} bbox The boundng box to modify
* @param {boolean} display True for display-mode fractions
*/
public getBevelledBBox(bbox: BBox, display: boolean) {
const {u, v, delta, nbox, dbox} = this.getBevelData(display);
const lbox = this.bevel.getOuterBBox();
bbox.combine(nbox, 0, u);
bbox.combine(lbox, bbox.w - delta / 2, 0);
bbox.combine(dbox, bbox.w - delta / 2, v);
}
/**
* @param {boolean} display True for display-style fractions
* @return {Object} The height (H) of the bevel, horizontal offest (delta)
* vertical offsets (u and v) of the parts, and
* bounding boxes of the parts.
*/
public getBevelData(display: boolean): {
H: number, delta: number, u: number, v: number, nbox: BBox, dbox: BBox
} {
const nbox = this.childNodes[0].getOuterBBox();
const dbox = this.childNodes[1].getOuterBBox();
const delta = (display ? .4 : .15);
const H = Math.max(nbox.scale * (nbox.h + nbox.d), dbox.scale * (dbox.h + dbox.d)) + 2 * delta;
const a = this.font.params.axis_height;
const u = nbox.scale * (nbox.d - nbox.h) / 2 + a + delta;
const v = dbox.scale * (dbox.d - dbox.h) / 2 + a - delta;
return {H, delta, u, v, nbox, dbox};
}
/************************************************/
/**
* @override
*/
public canStretch(_direction: DIRECTION) {
return false;
}
/**
* @return {boolean} True if in display mode, false otherwise
*/
public isDisplay(): boolean {
const {displaystyle, scriptlevel} = this.node.attributes.getList('displaystyle', 'scriptlevel');
return displaystyle && scriptlevel === 0;
}
/**
* @override
*/
public getWrapWidth(i: number) {
const attributes = this.node.attributes;
if (attributes.get('bevelled')) {
return this.childNodes[i].getOuterBBox().w;
}
const w = this.getBBox().w;
const thickness = this.length2em(attributes.get('linethickness'));
return w - (thickness ? .2 : 0) - 2 * this.pad;
}
/**
* @override
*/
public getChildAlign(i: number) {
const attributes = this.node.attributes;
return (attributes.get('bevelled') ? 'left' : attributes.get(['numalign', 'denomalign'][i]) as string);
}
};
}

View File

@@ -0,0 +1,137 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 CommonMglyph wrapper mixin for the MmlMglyph object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {CommonTextNode} from './TextNode.js';
import {TextNode} from '../../../core/MmlTree/MmlNode.js';
import {BBox} from '../../../util/BBox.js';
/*****************************************************************/
/**
* The CommonMglyph interface
*/
export interface CommonMglyph extends AnyWrapper {
/**
* The image's width converted to em's
*/
width: number;
/**
* The image's height converted to em's
*/
height: number;
/*
* The image's valign values converted to em's
*/
valign: number;
/**
* TextNode used for deprecated fontfamily/index use case
*/
charWrapper: CommonTextNode;
/**
* Obtain the width, height, and valign.
* Note: Currently, the width and height must be specified explicitly, or they default to 1em
* Since loading the image may be asynchronous, it would require a restart.
* A future extension could implement this either by subclassing this object, or
* perhaps as a post-filter on the MathML input jax that adds the needed dimensions
*/
getParameters(): void;
}
/**
* Shorthand for the CommonMglyph constructor
*/
export type MglyphConstructor = Constructor<CommonMglyph>;
/*****************************************************************/
/**
* The CommonMglyph wrapper mixin for the MmlMglyph object
*
* @template T The Wrapper class constructor type
*/
export function CommonMglyphMixin<T extends WrapperConstructor>(Base: T): MglyphConstructor & T {
return class extends Base {
/**
* @override
*/
public width: number;
/**
* @override
*/
public height: number;
/**
* @override
*/
public valign: number;
/**
* @override
*/
public charWrapper: CommonTextNode;
/**
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
this.getParameters();
}
/**
* @override
*/
public getParameters() {
const {width, height, valign, src, index} =
this.node.attributes.getList('width', 'height', 'valign', 'src', 'index');
if (src) {
this.width = (width === 'auto' ? 1 : this.length2em(width));
this.height = (height === 'auto' ? 1 : this.length2em(height));
this.valign = this.length2em(valign || '0');
} else {
const text = String.fromCodePoint(parseInt(index as string));
const mmlFactory = this.node.factory;
this.charWrapper = this.wrap((mmlFactory.create('text') as TextNode).setText(text));
this.charWrapper.parent = this as any as AnyWrapper;
}
}
/**
* @override
*/
public computeBBox(bbox: BBox, _recompute: boolean = false) {
if (this.charWrapper) {
bbox.updateFrom(this.charWrapper.getBBox());
} else {
bbox.w = this.width;
bbox.h = this.height + this.valign;
bbox.d = -this.valign;
}
}
};
}

View File

@@ -0,0 +1,58 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMi wrapper mixin for the MmlMi object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {BBox} from '../../../util/BBox.js';
/*****************************************************************/
/**
* The CommonMi interface
*/
export interface CommonMi extends AnyWrapper {
}
/**
* Shorthand for the CommonMi constructor
*/
export type MiConstructor = Constructor<CommonMi>;
/*****************************************************************/
/**
* The CommonMi wrapper mixin for the MmlMi object
*
* @template T The Wrapper class constructor type
*/
export function CommonMiMixin<T extends WrapperConstructor>(Base: T): MiConstructor & T {
return class extends Base {
/**
* @override
*/
public computeBBox(bbox: BBox, _recompute: boolean = false) {
super.computeBBox(bbox);
this.copySkewIC(bbox);
}
};
}

View File

@@ -0,0 +1,344 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 CommonMmultiscripts wrapper mixin for the MmlMmultiscripts object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, Constructor} from '../Wrapper.js';
import {CommonMsubsup, MsubsupConstructor} from './msubsup.js';
import {BBox} from '../../../util/BBox.js';
/*****************************************************************/
/**
* The data about the scripts and base
*/
export type ScriptData = {
base: BBox;
sub: BBox; // combined bbox for all subscripts
sup: BBox; // combined bbox for all superscripts
psub: BBox; // combined bbox for all presubscripts
psup: BBox; // combined bbox for all presuperscripts
numPrescripts: number;
numScripts: number;
};
export type ScriptDataName = keyof ScriptData;
/**
* The lists of all the individual script bboxes
*/
export type ScriptLists = {
base: BBox[];
subList: BBox[];
supList: BBox[];
psubList: BBox[];
psupList: BBox[];
};
export type ScriptListName = keyof ScriptLists;
/**
* The type of script that follows the given type
*/
export const NextScript: {[key: string]: ScriptListName} = {
base: 'subList',
subList: 'supList',
supList: 'subList',
psubList: 'psupList',
psupList: 'psubList',
};
/**
* The names of the scripts (for looping)
*/
export const ScriptNames = ['sup', 'sup', 'psup', 'psub'] as ScriptDataName[];
/*****************************************************************/
/**
* The CommonMmultiscripts interface
*
* @template W The child-node Wrapper class
*/
export interface CommonMmultiscripts<W extends AnyWrapper> extends CommonMsubsup<W> {
/**
* The cached data for the various bounding boxes
*/
scriptData: ScriptData;
/**
* The index of the child following the <mprescripts/> tag
*/
firstPrescript: number;
/**
* @param {BBox} pre The prescript bounding box
* @param {BBox} post The postcript bounding box
* @return {BBox} The combined bounding box
*/
combinePrePost(pre: BBox, post: BBox): BBox;
/**
* Compute the bounding box information about all the scripts
*/
getScriptData(): void;
/**
* @return {ScriptLists} The bounding boxes for all the scripts divided into lists by position
*/
getScriptBBoxLists(): ScriptLists;
/**
* Pad the second list, if it is one short
*
* @param {BBox[]} list1 The first list
* @param {BBox[]} list2 The second list
*/
padLists(list1: BBox[], list2: BBox[]): void;
/**
* @param {BBox} bbox1 The bbox for the combined subscripts
* @param {BBox} bbox2 The bbox for the combined superscripts
* @param {BBox[]} list1 The list of subscripts to combine
* @param {BBox[]} list2 The list of superscripts to combine
*/
combineBBoxLists(bbox1: BBox, bbox2: BBox, list1: BBox[], list2: BBox[]): void;
/**
* @param {BBox} bbox The bounding box from which to get the (scaled) width, height, and depth
*/
getScaledWHD(bbox: BBox): void;
}
/**
* Shorthand for the CommonMmultiscripts constructor
*
* @template W The child-node Wrapper class
*/
export type MmultiscriptsConstructor<W extends AnyWrapper> = Constructor<CommonMmultiscripts<W>>;
/*****************************************************************/
/**
* The CommonMmultiscripts wrapper mixin for the MmlMmultiscripts object
*
* @template W The child-node Wrapper class
* @template T The Wrapper class constructor type
*/
export function CommonMmultiscriptsMixin<
W extends AnyWrapper,
T extends MsubsupConstructor<W>
>(Base: T): MmultiscriptsConstructor<W> & T {
return class extends Base {
/**
* The cached data for the various bounding boxes
*/
public scriptData: ScriptData = null;
/**
* The index of the child following the <mprescripts/> tag
*/
public firstPrescript = 0;
/**
* @override
*/
constructor(...args: any[]) {
super(...args);
this.getScriptData();
}
/*************************************************************/
/**
* @param {BBox} pre The prescript bounding box
* @param {BBox} post The postcript bounding box
* @return {BBox} The combined bounding box
*/
public combinePrePost(pre: BBox, post: BBox): BBox {
const bbox = new BBox(pre);
bbox.combine(post, 0, 0);
return bbox;
}
/*************************************************************/
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
//
// Get the bounding boxes, and combine the pre- and post-scripts
// to get a common offset for both
//
const scriptspace = this.font.params.scriptspace;
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(sub, sup);
//
// Lay out the pre-scripts, then the base, then the post-scripts
//
bbox.empty();
if (data.numPrescripts) {
bbox.combine(data.psup, scriptspace, u);
bbox.combine(data.psub, scriptspace, v);
}
bbox.append(data.base);
if (data.numScripts) {
const w = bbox.w;
bbox.combine(data.sup, w, u);
bbox.combine(data.sub, w, v);
bbox.w += scriptspace;
}
bbox.clean();
this.setChildPWidths(recompute);
}
/**
* Compute the bounding box information about all the scripts
*/
public getScriptData() {
//
// Initialize the bounding box data
//
const data: ScriptData = this.scriptData = {
base: null, sub: BBox.empty(), sup: BBox.empty(), psub: BBox.empty(), psup: BBox.empty(),
numPrescripts: 0, numScripts: 0
};
//
// Get the bboxes for all the scripts and combine them into the scriptData
//
const lists = this.getScriptBBoxLists();
this.combineBBoxLists(data.sub, data.sup, lists.subList, lists.supList);
this.combineBBoxLists(data.psub, data.psup, lists.psubList, lists.psupList);
data.base = lists.base[0];
//
// Save the lengths and return the data
//
data.numPrescripts = lists.psubList.length;
data.numScripts = lists.subList.length;
}
/**
* @return {ScriptLists} The bounding boxes for all the scripts divided into lists by position
*/
public getScriptBBoxLists(): ScriptLists {
const lists: ScriptLists = {
base: [], subList: [], supList: [], psubList: [], psupList: []
};
//
// The first entry is the base, and then they altername sub- and superscripts.
// Once we find the <mprescripts/> element, switch to presub- and presuperscript lists.
//
let script: ScriptListName = 'base';
for (const child of this.childNodes) {
if (child.node.isKind('mprescripts')) {
script = 'psubList';
} else {
lists[script].push(child.getOuterBBox());
script = NextScript[script];
}
}
//
// The index of the first prescript (skip over base, sub- and superscripts, and mprescripts)
//
this.firstPrescript = lists.subList.length + lists.supList.length + 2;
//
// Make sure the lists are the same length
//
this.padLists(lists.subList, lists.supList);
this.padLists(lists.psubList, lists.psupList);
return lists;
}
/**
* Pad the second list, if it is one short
*
* @param {BBox[]} list1 The first list
* @param {BBox[]} list2 The second list
*/
public padLists(list1: BBox[], list2: BBox[]) {
if (list1.length > list2.length) {
list2.push(BBox.empty());
}
}
/**
* @param {BBox} bbox1 The bbox for the combined subscripts
* @param {BBox} bbox2 The bbox for the combined superscripts
* @param {BBox[]} list1 The list of subscripts to combine
* @param {BBox[]} list2 The list of superscripts to combine
*/
public combineBBoxLists(bbox1: BBox, bbox2: BBox, list1: BBox[], list2: BBox[]) {
for (let i = 0; i < list1.length; i++) {
const [w1, h1, d1] = this.getScaledWHD(list1[i]);
const [w2, h2, d2] = this.getScaledWHD(list2[i]);
const w = Math.max(w1, w2);
bbox1.w += w;
bbox2.w += w;
if (h1 > bbox1.h) bbox1.h = h1;
if (d1 > bbox1.d) bbox1.d = d1;
if (h2 > bbox2.h) bbox2.h = h2;
if (d2 > bbox2.d) bbox2.d = d2;
}
}
/**
* @param {BBox} bbox The bounding box from which to get the (scaled) width, height, and depth
*/
public getScaledWHD(bbox: BBox) {
const {w, h, d, rscale} = bbox;
return [w * rscale, h * rscale, d * rscale];
}
/*************************************************************/
/**
* @override
*/
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();
} else if (supbox.h === 0 && supbox.d === 0) {
//
// Use placement for subsccript only
//
u = -this.getV();
} else {
//
// Use placement for both
//
[u, v, q] = super.getUVQ(subbox, supbox);
}
this.UVQ = [u, v, q];
}
return this.UVQ;
}
};
}

View File

@@ -0,0 +1,70 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 CommonMn wrapper mixin for the MmlMn object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
/*****************************************************************/
/**
* The CommonMn interface
*/
export interface CommonMn extends AnyWrapper {
}
/**
* Shorthand for the CommonMn constructor
*/
export type MnConstructor = Constructor<CommonMn>;
/*****************************************************************/
/**
* The CommonMn wrapper mixin for the MmlMn object
*
* @template T The Wrapper class constructor type
*/
export function CommonMnMixin<T extends WrapperConstructor>(Base: T): MnConstructor & T {
return class extends Base {
/**
* @override
*/
public remapChars(chars: number[]) {
//
// Convert a leading hyphen to a minus
//
if (chars.length) {
const text = this.font.getRemappedChar('mn', chars[0]);
if (text) {
const c = this.unicodeChars(text, this.variant);
if (c.length === 1) {
chars[0] = c[0];
} else {
chars = c.concat(chars.slice(1));
}
}
}
return chars;
}
};
}

View File

@@ -0,0 +1,413 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMo wrapper mixin for the MmlMo object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {MmlMo} from '../../../core/MmlTree/MmlNodes/mo.js';
import {BBox} from '../../../util/BBox.js';
import {unicodeChars} from '../../../util/string.js';
import {DelimiterData} from '../FontData.js';
import {DIRECTION, NOSTRETCH} from '../FontData.js';
/*****************************************************************/
/**
* Convert direction to letter
*/
export const DirectionVH: {[n: number]: string} = {
[DIRECTION.Vertical]: 'v',
[DIRECTION.Horizontal]: 'h'
};
/*****************************************************************/
/**
* The CommonMo interface
*/
export interface CommonMo extends AnyWrapper {
/**
* The font size that a stretched operator uses.
* If -1, then stretch arbitrarily, and bbox gives the actual height, depth, width
*/
size: number;
/**
* True if used as an accent in an munderover construct
*/
isAccent: boolean;
/**
* Get the (unmodified) bbox of the contents (before centering or setting accents to width 0)
*
* @param {BBox} bbox The bbox to fill
*/
protoBBox(bbox: BBox): void;
/**
* @return {number} Offset to the left by half the actual width of the accent
*/
getAccentOffset(): number;
/**
* @param {BBox} bbox The bbox to center, or null to compute the bbox
* @return {number} The offset to move the glyph to center it
*/
getCenterOffset(bbox?: BBox): number;
/**
* Determint variant for vertically/horizontally stretched character
*
* @param {number[]} WH size to stretch to, either [W] or [H, D]
* @param {boolean} exact True if not allowed to use delimiter factor and shortfall
*/
getStretchedVariant(WH: number[], exact?: boolean): void;
/**
* @param {string} name The name of the attribute to get
* @param {number} value The default value to use
* @return {number} The size in em's of the attribute (or the default value)
*/
getSize(name: string, value: number): number;
/**
* @param {number[]} WH Either [W] for width, [H, D] for height and depth, or [] for min/max size
* @return {number} Either the width or the total height of the character
*/
getWH(WH: number[]): number;
/**
* @param {number[]} WHD The [W] or [H, D] being requested from the parent mrow
* @param {number} D The full dimension (including symmetry, etc)
* @param {DelimiterData} C The delimiter data for the stretchy character
*/
getStretchBBox(WHD: number[], D: number, C: DelimiterData): void;
/**
* @param {number[]} WHD The [H, D] being requested from the parent mrow
* @param {number} HD The full height (including symmetry, etc)
* @param {DelimiterData} C The delimiter data for the stretchy character
* @return {number[]} The height and depth for the vertically stretched delimiter
*/
getBaseline(WHD: number[], HD: number, C: DelimiterData): number[];
/**
* Determine the size of the delimiter based on whether full extenders should be used or not.
*
* @param {number} D The requested size of the delimiter
* @param {DelimiterData} C The data for the delimiter
* @return {number} The final size of the assembly
*/
checkExtendedHeight(D: number, C: DelimiterData): number;
}
/**
* Shorthand for the CommonMo constructor
*/
export type MoConstructor = Constructor<CommonMo>;
/*****************************************************************/
/**
* The CommomMo wrapper mixin for the MmlMo object
*
* @template T The Wrapper class constructor type
*/
export function CommonMoMixin<T extends WrapperConstructor>(Base: T): MoConstructor & T {
return class extends Base {
/**
* The font size that a stretched operator uses.
* If -1, then stretch arbitrarily, and bbox gives the actual height, depth, width
*/
public size: number = null;
/**
* True if used as an accent in an munderover construct
*/
public isAccent: boolean;
/**
* @override
*/
constructor(...args: any[]) {
super(...args);
this.isAccent = (this.node as MmlMo).isAccent;
}
/**
* @override
*/
public computeBBox(bbox: BBox, _recompute: boolean = false) {
this.protoBBox(bbox);
if (this.node.attributes.get('symmetric') &&
this.stretch.dir !== DIRECTION.Horizontal) {
const d = this.getCenterOffset(bbox);
bbox.h += d;
bbox.d -= d;
}
if (this.node.getProperty('mathaccent') &&
(this.stretch.dir === DIRECTION.None || this.size >= 0)) {
bbox.w = 0;
}
}
/**
* Get the (unmodified) bbox of the contents (before centering or setting accents to width 0)
*
* @param {BBox} bbox The bbox to fill
*/
public protoBBox(bbox: BBox) {
const stretchy = (this.stretch.dir !== DIRECTION.None);
if (stretchy && this.size === null) {
this.getStretchedVariant([0]);
}
if (stretchy && this.size < 0) return;
super.computeBBox(bbox);
this.copySkewIC(bbox);
}
/**
* @return {number} Offset to the left by half the actual width of the accent
*/
public getAccentOffset(): number {
const bbox = BBox.empty();
this.protoBBox(bbox);
return -bbox.w / 2;
}
/**
* @param {BBox} bbox The bbox to center, or null to compute the bbox
* @return {number} The offset to move the glyph to center it
*/
public getCenterOffset(bbox: BBox = null): number {
if (!bbox) {
bbox = BBox.empty();
super.computeBBox(bbox);
}
return ((bbox.h + bbox.d) / 2 + this.font.params.axis_height) - bbox.h;
}
/**
* @override
*/
public getVariant() {
if (this.node.attributes.get('largeop')) {
this.variant = (this.node.attributes.get('displaystyle') ? '-largeop' : '-smallop');
return;
}
if (!this.node.attributes.getExplicit('mathvariant') &&
this.node.getProperty('pseudoscript') === false) {
this.variant = '-tex-variant';
return;
}
super.getVariant();
}
/**
* @override
*/
public canStretch(direction: DIRECTION) {
if (this.stretch.dir !== DIRECTION.None) {
return this.stretch.dir === direction;
}
const attributes = this.node.attributes;
if (!attributes.get('stretchy')) return false;
const c = this.getText();
if (Array.from(c).length !== 1) return false;
const delim = this.font.getDelimiter(c.codePointAt(0));
this.stretch = (delim && delim.dir === direction ? delim : NOSTRETCH);
return this.stretch.dir !== DIRECTION.None;
}
/**
* Determint variant for vertically/horizontally stretched character
*
* @param {number[]} WH size to stretch to, either [W] or [H, D]
* @param {boolean} exact True if not allowed to use delimiter factor and shortfall
*/
public getStretchedVariant(WH: number[], exact: boolean = false) {
if (this.stretch.dir !== DIRECTION.None) {
let D = this.getWH(WH);
const min = this.getSize('minsize', 0);
const max = this.getSize('maxsize', Infinity);
const mathaccent = this.node.getProperty('mathaccent');
//
// Clamp the dimension to the max and min
// then get the target size via TeX rules
//
D = Math.max(min, Math.min(max, D));
const df = this.font.params.delimiterfactor / 1000;
const ds = this.font.params.delimitershortfall;
const m = (min || exact ? D : mathaccent ? Math.min(D / df, D + ds) : Math.max(D * df, D - ds));
//
// Look through the delimiter sizes for one that matches
//
const delim = this.stretch;
const c = delim.c || this.getText().codePointAt(0);
let i = 0;
if (delim.sizes) {
for (const d of delim.sizes) {
if (d >= m) {
if (mathaccent && i) {
i--;
}
this.variant = this.font.getSizeVariant(c, i);
this.size = i;
if (delim.schar && delim.schar[i]) {
this.stretch = {...this.stretch, c: delim.schar[i]};
}
return;
}
i++;
}
}
//
// No size matches, so if we can make multi-character delimiters,
// record the data for that, otherwise, use the largest fixed size.
//
if (delim.stretch) {
this.size = -1;
this.invalidateBBox();
this.getStretchBBox(WH, this.checkExtendedHeight(D, delim), delim);
} else {
this.variant = this.font.getSizeVariant(c, i - 1);
this.size = i - 1;
}
}
}
/**
* @param {string} name The name of the attribute to get
* @param {number} value The default value to use
* @return {number} The size in em's of the attribute (or the default value)
*/
public getSize(name: string, value: number): number {
let attributes = this.node.attributes;
if (attributes.isSet(name)) {
value = this.length2em(attributes.get(name), 1, 1); // FIXME: should use height of actual character
}
return value;
}
/**
* @param {number[]} WH Either [W] for width, [H, D] for height and depth, or [] for min/max size
* @return {number} Either the width or the total height of the character
*/
public getWH(WH: number[]): number {
if (WH.length === 0) return 0;
if (WH.length === 1) return WH[0];
let [H, D] = WH;
const a = this.font.params.axis_height;
return (this.node.attributes.get('symmetric') ? 2 * Math.max(H - a, D + a) : H + D);
}
/**
* @param {number[]} WHD The [W] or [H, D] being requested from the parent mrow
* @param {number} D The full dimension (including symmetry, etc)
* @param {DelimiterData} C The delimiter data for the stretchy character
*/
public getStretchBBox(WHD: number[], D: number, C: DelimiterData) {
if (C.hasOwnProperty('min') && C.min > D) {
D = C.min;
}
let [h, d, w] = C.HDW;
if (this.stretch.dir === DIRECTION.Vertical) {
[h, d] = this.getBaseline(WHD, D, C);
} else {
w = D;
}
this.bbox.h = h;
this.bbox.d = d;
this.bbox.w = w;
}
/**
* @param {number[]} WHD The [H, D] being requested from the parent mrow
* @param {number} HD The full height (including symmetry, etc)
* @param {DelimiterData} C The delimiter data for the stretchy character
* @return {[number, number]} The height and depth for the vertically stretched delimiter
*/
public getBaseline(WHD: number[], HD: number, C: DelimiterData): [number, number] {
const hasWHD = (WHD.length === 2 && WHD[0] + WHD[1] === HD);
const symmetric = this.node.attributes.get('symmetric');
const [H, D] = (hasWHD ? WHD : [HD, 0]);
let [h, d] = [H + D, 0];
if (symmetric) {
//
// Center on the math axis
//
const a = this.font.params.axis_height;
if (hasWHD) {
h = 2 * Math.max(H - a, D + a);
}
d = h / 2 - a;
} else if (hasWHD) {
//
// Use the given depth (from mrow)
//
d = D;
} else {
//
// Use depth proportional to the normal-size character
// (when stretching for minsize or maxsize by itself)
//
let [ch, cd] = (C.HDW || [.75, .25]);
d = cd * (h / (ch + cd));
}
return [h - d, d];
}
/**
* @override
*/
public checkExtendedHeight(D: number, C: DelimiterData): number {
if (C.fullExt) {
const [extSize, endSize] = C.fullExt;
const n = Math.ceil(Math.max(0, D - endSize) / extSize);
D = endSize + n * extSize;
}
return D;
}
/**
* @override
*/
public remapChars(chars: number[]) {
const primes = this.node.getProperty('primes') as string;
if (primes) {
return unicodeChars(primes);
}
if (chars.length === 1) {
const parent = (this.node as MmlMo).coreParent().parent;
const isAccent = this.isAccent && !parent.isKind('mrow');
const map = (isAccent ? 'accent' : 'mo');
const text = this.font.getRemappedChar(map, chars[0]);
if (text) {
chars = this.unicodeChars(text, this.variant);
}
}
return chars;
}
};
}

View File

@@ -0,0 +1,145 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMpadded wrapper mixin for the MmlMpadded object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {BBox} from '../../../util/BBox.js';
import {Property} from '../../../core/Tree/Node.js';
/*****************************************************************/
/**
* The CommonMpadded interface
*/
export interface CommonMpadded extends AnyWrapper {
/**
* Get the content bounding box, and the change in size and offsets
* as specified by the parameters
*
* @return {number[]} The original height, depth, width, the changes in height, depth,
* and width, and the horizontal and vertical offsets of the content
*/
getDimens(): number[];
/**
* Get a particular dimension, which can be relative to any of the BBox dimensions,
* and can be an offset from the default size of the given dimension.
*
* @param {Property} length The value to be converted to a length in ems
* @param {BBox} bbox The bbox of the mpadded content
* @param {string=} d The default dimension to use for relative sizes ('w', 'h', or 'd')
* @param {number=} m The minimum value allowed for the dimension
* @return {number} The final dimension in ems
*/
dimen(length: Property, bbox: BBox, d?: string, m?: number): number;
}
/**
* Shorthand for the CommonMpadded constructor
*/
export type MpaddedConstructor = Constructor<CommonMpadded>;
/*****************************************************************/
/**
* The CommomMpadded wrapper for the MmlMpadded object
*
* @template T The Wrapper class constructor type
*/
export function CommonMpaddedMixin<T extends WrapperConstructor>(Base: T): MpaddedConstructor & T {
return class extends Base {
/**
* Get the content bounding box, and the change in size and offsets
* as specified by the parameters
*
* @return {number[]} The original height, depth, width, the changes in height, depth,
* and width, and the horizontal and vertical offsets of the content
*/
public getDimens(): number[] {
const values = this.node.attributes.getList('width', 'height', 'depth', 'lspace', 'voffset');
const bbox = this.childNodes[0].getBBox(); // get unmodified bbox of children
let {w, h, d} = bbox;
let W = w, H = h, D = d, x = 0, y = 0, dx = 0;
if (values.width !== '') w = this.dimen(values.width, bbox, 'w', 0);
if (values.height !== '') h = this.dimen(values.height, bbox, 'h', 0);
if (values.depth !== '') d = this.dimen(values.depth, bbox, 'd', 0);
if (values.voffset !== '') y = this.dimen(values.voffset, bbox);
if (values.lspace !== '') x = this.dimen(values.lspace, bbox);
const align = this.node.attributes.get('data-align') as string;
if (align) {
dx = this.getAlignX(w, bbox, align);
}
return [H, D, W, h - H, d - D, w - W, x, y, dx];
}
/**
* Get a particular dimension, which can be relative to any of the BBox dimensions,
* and can be an offset from the default size of the given dimension.
*
* @param {Property} length The value to be converted to a length in ems
* @param {BBox} bbox The bbox of the mpadded content
* @param {string} d The default dimension to use for relative sizes ('w', 'h', or 'd')
* @param {number} m The minimum value allowed for the dimension
* @return {number} The final dimension in ems
*/
public dimen(length: Property, bbox: BBox, d: string = '', m: number = null): number {
length = String(length);
const match = length.match(/width|height|depth/);
const size = (match ? bbox[match[0].charAt(0) as (keyof BBox)] :
(d ? bbox[d as (keyof BBox)] : 0)) as number;
let dimen = (this.length2em(length, size) || 0);
if (length.match(/^[-+]/) && d) {
dimen += size;
}
if (m != null) {
dimen = Math.max(m, dimen);
}
return dimen;
}
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
const [H, D, W, dh, dd, dw] = this.getDimens();
bbox.w = W + dw;
bbox.h = H + dh;
bbox.d = D + dd;
this.setChildPWidths(recompute, bbox.w);
}
/**
* @override
*/
public getWrapWidth(_i: number) {
return this.getBBox().w;
}
/**
* @override
*/
public getChildAlign(_i: number) {
return this.node.attributes.get('data-align') as string || 'left';
}
};
}

View File

@@ -0,0 +1,104 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMroot wrapper mixin for the MmlMroot object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {Constructor} from '../../common/Wrapper.js';
import {CommonMsqrt, MsqrtConstructor} from './msqrt.js';
import {CommonMo} from './mo.js';
import {BBox} from '../../../util/BBox.js';
/*****************************************************************/
/**
* The CommonMroot interface
*/
export interface CommonMroot extends CommonMsqrt {
}
/**
* Shorthand for the CommonMroot constructor
*/
export type MrootConstructor = Constructor<CommonMroot>;
/*****************************************************************/
/**
* The CommonMroot wrapper mixin for the MmlMroot object (extends CommonMsqrt)
*
* @template T The Wrapper class constructor type
*/
export function CommonMrootMixin<T extends MsqrtConstructor>(Base: T): MrootConstructor & T {
return class extends Base {
/**
* @override
*/
get surd() {
return 2;
}
/**
* @override
*/
get root(): number {
return 1;
}
/**
* @override
*/
public combineRootBBox(BBOX: BBox, sbox: BBox, H: number) {
const bbox = this.childNodes[this.root].getOuterBBox();
const h = this.getRootDimens(sbox, H)[1];
BBOX.combine(bbox, 0, h);
}
/**
* @override
*/
public getRootDimens(sbox: BBox, H: number) {
const surd = this.childNodes[this.surd] as CommonMo;
const bbox = this.childNodes[this.root].getOuterBBox();
const offset = (surd.size < 0 ? .5 : .6) * sbox.w;
const {w, rscale} = bbox;
const W = Math.max(w, offset / rscale);
const dx = Math.max(0, W - w);
const h = this.rootHeight(bbox, sbox, surd.size, H);
const x = W * rscale - offset;
return [x, h, dx];
}
/**
* @param {BBox} rbox The bbox of the root
* @param {BBox} sbox The bbox of the surd
* @param {number} size The size of the surd
* @param {number} H The height of the root as a whole
* @return {number} The height of the root within the surd
*/
public rootHeight(rbox: BBox, sbox: BBox, size: number, H: number): number {
const h = sbox.h + sbox.d;
const b = (size < 0 ? 1.9 : .55 * h) - (h - H);
return b + Math.max(0, rbox.d * rbox.rscale);
}
};
}

View File

@@ -0,0 +1,159 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMrow wrapper minin for the MmlMrow object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {CommonMo} from './mo.js';
import {BBox} from '../../../util/BBox.js';
import {DIRECTION} from '../FontData.js';
/*****************************************************************/
/**
* The CommonMrow interface
*/
export interface CommonMrow extends AnyWrapper {
/**
* Handle vertical stretching of children to match height of
* other nodes in the row.
*/
stretchChildren(): void;
}
/**
* Shorthand for the CommonMrow constructor
*/
export type MrowConstructor = Constructor<CommonMrow>;
/*****************************************************************/
/**
* The CommonMrow wrapper mixin for the MmlMrow object
*
* @template T The Wrapper class constructor type
*/
export function CommonMrowMixin<T extends WrapperConstructor>(Base: T): MrowConstructor & T {
return class extends Base {
/**
* @override
*/
get fixesPWidth() {
return false;
}
/**
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
this.stretchChildren();
for (const child of this.childNodes) {
if (child.bbox.pwidth) {
this.bbox.pwidth = BBox.fullWidth;
break;
}
}
}
/**
* Handle vertical stretching of children to match height of
* other nodes in the row.
*/
public stretchChildren() {
let stretchy: AnyWrapper[] = [];
//
// Locate and count the stretchy children
//
for (const child of this.childNodes) {
if (child.canStretch(DIRECTION.Vertical)) {
stretchy.push(child);
}
}
let count = stretchy.length;
let nodeCount = this.childNodes.length;
if (count && nodeCount > 1) {
let H = 0, D = 0;
//
// If all the children are stretchy, find the largest one,
// otherwise, find the height and depth of the non-stretchy
// children.
//
let all = (count > 1 && count === nodeCount);
for (const child of this.childNodes) {
const noStretch = (child.stretch.dir === DIRECTION.None);
if (all || noStretch) {
let {h, d, rscale} = child.getOuterBBox(noStretch);
h *= rscale;
d *= rscale;
if (h > H) H = h;
if (d > D) D = d;
}
}
//
// Stretch the stretchable children
//
for (const child of stretchy) {
(child.coreMO() as CommonMo).getStretchedVariant([H, D]);
}
}
}
};
}
/*****************************************************************/
/*****************************************************************/
/**
* The CommonInferredMrow interface
*/
export interface CommonInferredMrow extends CommonMrow {
}
/**
* Shorthand for the CommonInferredMrow constructor
*/
export type InferredMrowConstructor = Constructor<CommonInferredMrow>;
/*****************************************************************/
/**
* The CommonInferredMrow wrapper mixin for the MmlInferredMrow object
*
* @template T The Wrapper class constructor type
*/
export function CommonInferredMrowMixin<T extends MrowConstructor>(Base: T): InferredMrowConstructor & T {
return class extends Base {
/**
* Since inferred rows don't produce a container span, we can't
* set a font-size for it, so we inherit the parent scale
*
* @override
*/
public getScale() {
this.bbox.scale = this.parent.bbox.scale;
this.bbox.rscale = 1;
}
};
}

View File

@@ -0,0 +1,85 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMs wrapper mixin for the MmlMs object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
/*****************************************************************/
/**
* The CommonMs interface
*/
export interface CommonMs extends AnyWrapper {
/**
* Create a text wrapper with the given text;
*
* @param {string} text The text for the wrapped element
* @return {CommonWrapper} The wrapped text node
*/
createText(text: string): AnyWrapper;
}
/**
* Shorthand for the CommonMs constructor
*/
export type MsConstructor = Constructor<CommonMs>;
/*****************************************************************/
/**
* The CommonMs wrapper mixin for the MmlMs object
*
* @template T The Wrapper class constructor type
*/
export function CommonMsMixin<T extends WrapperConstructor>(Base: T): MsConstructor & T {
return class extends Base {
/**
* Add the quote characters to the wrapper children so they will be output
*
* @override
*/
constructor(...args: any[]) {
super(...args);
const attributes = this.node.attributes;
let quotes = attributes.getList('lquote', 'rquote');
if (this.variant !== 'monospace') {
if (!attributes.isSet('lquote') && quotes.lquote === '"') quotes.lquote = '\u201C';
if (!attributes.isSet('rquote') && quotes.rquote === '"') quotes.rquote = '\u201D';
}
this.childNodes.unshift(this.createText(quotes.lquote as string));
this.childNodes.push(this.createText(quotes.rquote as string));
}
/**
* Create a text wrapper with the given text;
*
* @param {string} text The text for the wrapped element
* @return {AnyWrapper} The wrapped text node
*/
public createText(text: string): AnyWrapper {
const node = this.wrap(this.mmlText(text));
node.parent = this;
return node;
}
};
}

View File

@@ -0,0 +1,69 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMspace wrapper mixin for the MmlMspace object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {BBox} from '../../../util/BBox.js';
/*****************************************************************/
/**
* The CommonMspance interface
*/
export interface CommonMspace extends AnyWrapper {
}
/**
* Shorthand for the CommonMspace constructor
*/
export type MspaceConstructor = Constructor<CommonMspace>;
/*****************************************************************/
/**
* The CommonMspace wrapper mixin for the MmlMspace object
*
* @template T The Wrapper class constructor type
*/
export function CommonMspaceMixin<T extends WrapperConstructor>(Base: T): MspaceConstructor & T {
return class extends Base {
/**
* @override
*/
public computeBBox(bbox: BBox, _recompute: boolean = false) {
const attributes = this.node.attributes;
bbox.w = this.length2em(attributes.get('width'), 0);
bbox.h = this.length2em(attributes.get('height'), 0);
bbox.d = this.length2em(attributes.get('depth'), 0);
}
/**
* No contents, so no need for variant class
*
* @override
*/
public handleVariant() {
}
};
}

View File

@@ -0,0 +1,197 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMsqrt wrapper for the MmlMsqrt object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {CommonMo} from './mo.js';
import {BBox} from '../../../util/BBox.js';
import {DIRECTION} from '../FontData.js';
/*****************************************************************/
/**
* The CommonMsqrt interface
*/
export interface CommonMsqrt extends AnyWrapper {
/**
* The index of the base of the root in childNodes
*/
readonly base: number;
/**
* The index of the surd in childNodes
*/
readonly surd: number;
/**
* The index of the root in childNodes (or null if none)
*/
readonly root: number;
/**
* The requested height of the stretched surd character
*/
surdH: number;
/**
* Combine the bounding box of the root (overridden in mroot)
*
* @param {BBox} bbox The bounding box so far
* @param {BBox} sbox The bounding box of the surd
* @param {number} H The height of the root as a whole
*/
combineRootBBox(bbox: BBox, sbox: BBox, H: number): void;
/**
* @param {BBox} sbox The bounding box for the surd character
* @return {number[]} The p, q, and x values for the TeX layout computations
*/
getPQ(sbox: BBox): number[];
/**
* @param {BBox} sbox The bounding box of the surd
* @param {number} H The height of the root as a whole
* @return {number[]} The x offset of the surd, and the height, x offset, and scale of the root
*/
getRootDimens(sbox: BBox, H: Number): number[];
}
/**
* Shorthand for the CommonMsqrt constructor
*/
export type MsqrtConstructor = Constructor<CommonMsqrt>;
/*****************************************************************/
/**
* The CommonMsqrt wrapper mixin for the MmlMsqrt object
*
* @template T The Wrapper class constructor type
*/
export function CommonMsqrtMixin<T extends WrapperConstructor>(Base: T): MsqrtConstructor & T {
return class extends Base {
/**
* @return {number} The index of the base of the root in childNodes
*/
get base(): number {
return 0;
}
/**
* @return {number} The index of the surd in childNodes
*/
get surd(): number {
return 1;
}
/**
* @return {number} The index of the root in childNodes (or null if none)
*/
get root(): number {
return null;
}
/**
* The requested height of the stretched surd character
*/
public surdH: number;
/**
* Add the surd character so we can display it later
*
* @override
*/
constructor(...args: any[]) {
super(...args);
const surd = this.createMo('\u221A');
surd.canStretch(DIRECTION.Vertical);
const {h, d} = this.childNodes[this.base].getOuterBBox();
const t = this.font.params.rule_thickness;
const p = (this.node.attributes.get('displaystyle') ? this.font.params.x_height : t);
this.surdH = h + d + 2 * t + p / 4;
(surd as CommonMo).getStretchedVariant([this.surdH - d, d], true);
}
/**
* @override
*/
public createMo(text: string) {
const node = super.createMo(text);
this.childNodes.push(node);
return node;
}
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
const surdbox = this.childNodes[this.surd].getBBox();
const basebox = new BBox(this.childNodes[this.base].getOuterBBox());
const q = this.getPQ(surdbox)[1];
const t = this.font.params.rule_thickness;
const H = basebox.h + q + t;
const [x] = this.getRootDimens(surdbox, H);
bbox.h = H + t;
this.combineRootBBox(bbox, surdbox, H);
bbox.combine(surdbox, x, H - surdbox.h);
bbox.combine(basebox, x + surdbox.w, 0);
bbox.clean();
this.setChildPWidths(recompute);
}
/**
* Combine the bounding box of the root (overridden in mroot)
*
* @param {BBox} bbox The bounding box so far
* @param {BBox} sbox The bounding box of the surd
* @param {number} H The height of the root as a whole
*/
public combineRootBBox(_bbox: BBox, _sbox: BBox, _H: number) {
}
/**
* @param {BBox} sbox The bounding box for the surd character
* @return {[number, number]} The p, q, and x values for the TeX layout computations
*/
public getPQ(sbox: BBox): [number, number] {
const t = this.font.params.rule_thickness;
const p = (this.node.attributes.get('displaystyle') ? this.font.params.x_height : t);
const q = (sbox.h + sbox.d > this.surdH ?
((sbox.h + sbox.d) - (this.surdH - 2 * t - p / 2)) / 2 :
t + p / 4);
return [p, q];
}
/**
* @param {BBox} sbox The bounding box of the surd
* @param {number} H The height of the root as a whole
* @return {[number, number, number, number]} The x offset of the surd, and
* the height, x offset, and scale of the root
*/
public getRootDimens(_sbox: BBox, _H: number): [number, number, number, number] {
return [0, 0, 0, 0];
}
};
}

View File

@@ -0,0 +1,286 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMsubsup wrapper mixin for the MmlMsubsup object
* and the special cases CommonMsub and CommonMsup
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, Constructor} from '../Wrapper.js';
import {CommonScriptbase, ScriptbaseConstructor} from './scriptbase.js';
import {BBox} from '../../../util/BBox.js';
import {MmlMsubsup, MmlMsub, MmlMsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
/*****************************************************************/
/**
* The CommonMsub interface
*
* @template W The child-node Wrapper class
*/
export interface CommonMsub<W extends AnyWrapper> extends CommonScriptbase<W> {
}
/**
* Shorthand for the CommonMsub constructor
*
* @template W The child-node Wrapper class
*/
export type MsubConstructor<W extends AnyWrapper> = Constructor<CommonMsub<W>>;
/*****************************************************************/
/**
* The CommonMsub wrapper mixin for the MmlMsub object
*
* @template W The child-node Wrapper class
* @template T The Wrapper class constructor type
*/
export function CommonMsubMixin<
W extends AnyWrapper,
T extends ScriptbaseConstructor<W>
>(Base: T): MsubConstructor<W> & T {
return class extends Base {
/**
* Do not include italic correction
*/
public static useIC: boolean = false;
/**
* @override
*/
public get scriptChild() {
return this.childNodes[(this.node as MmlMsub).sub];
}
/**
* Get the shift for the subscript
*
* @override
*/
public getOffset() {
return [0, -this.getV()];
}
};
}
/*****************************************************************/
/**
* The CommonMsup interface
*
* @template W The child-node Wrapper class
*/
export interface CommonMsup<W extends AnyWrapper> extends CommonScriptbase<W> {
}
/**
* Shorthand for the CommonMsup constructor
*
* @template W The child-node Wrapper class
*/
export type MsupConstructor<W extends AnyWrapper> = Constructor<CommonMsup<W>>;
/*****************************************************************/
/**
* The CommonMsup wrapper mixin for the MmlMsup object
*
* @template W The child-node Wrapper class
* @template T The Wrapper class constructor type
*/
export function CommonMsupMixin<
W extends AnyWrapper,
T extends ScriptbaseConstructor<W>
>(Base: T): MsupConstructor<W> & T {
return class extends Base {
/**
* @override
*/
public get scriptChild() {
return this.childNodes[(this.node as MmlMsup).sup];
}
/**
* Get the shift for the superscript
*
* @override
*/
public getOffset() {
const x = this.getAdjustedIc() - (this.baseRemoveIc ? 0 : this.baseIc);
return [x, this.getU()];
}
};
}
/*****************************************************************/
/**
* The CommonMsubsup interface
*
* @template W The child-node Wrapper class
*/
export interface CommonMsubsup<W extends AnyWrapper> extends CommonScriptbase<W> {
/**
* Cached values for the script offsets and separation (so if they are
* computed in computeBBox(), they don't have to be recomputed during output)
*/
UVQ: number[];
/**
* The wrapper for the subscript
*/
readonly subChild: W;
/**
* The wrapper for the superscript
*/
readonly supChild: W;
/**
* Get the shift for the scripts and their separation (TeXBook Appendix G 18adef)
*
* @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(subbox?: BBox, supbox?: BBox): number[];
}
/**
* Shorthand for the CommonMsubsup constructor
*
* @template W The child-node Wrapper class
*/
export type MsubsupConstructor<W extends AnyWrapper> = Constructor<CommonMsubsup<W>>;
/*****************************************************************/
/**
* The CommomMsubsup wrapper for the MmlMsubsup object
*
* @template W The child-node Wrapper class
* @template T The Wrapper class constructor type
*/
export function CommonMsubsupMixin<
W extends AnyWrapper,
T extends ScriptbaseConstructor<W>
>(Base: T): MsubsupConstructor<W> & T {
return class extends Base {
/**
* Do not include italic correction
*/
public static useIC: boolean = false;
/**
* Cached values for the script offsets and separation (so if they are
* computed in computeBBox(), they don't have to be recomputed during output)
*/
public UVQ: number[] = null;
/**
* @return {W} The wrapper for the subscript
*/
public get subChild(): W {
return this.childNodes[(this.node as MmlMsubsup).sub];
}
/**
* @return {W} The wrapper for the superscript
*/
public get supChild(): W {
return this.childNodes[(this.node as MmlMsubsup).sup];
}
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
const basebox = this.baseChild.getOuterBBox();
const [subbox, supbox] = [this.subChild.getOuterBBox(), this.supChild.getOuterBBox()];
bbox.empty();
bbox.append(basebox);
const w = this.getBaseWidth();
const x = this.getAdjustedIc();
const [u, v] = this.getUVQ();
bbox.combine(subbox, w, v);
bbox.combine(supbox, w + x, u);
bbox.w += this.font.params.scriptspace;
bbox.clean();
this.setChildPWidths(recompute);
}
/**
* Get the shift for the scripts and their separation (TeXBook Appendix G 18adef)
*
* @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(
subbox: BBox = this.subChild.getOuterBBox(),
supbox: BBox = this.supChild.getOuterBBox()
): number[] {
const basebox = this.baseCore.getOuterBBox();
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.baseCharZero(basebox.d * this.baseScale + 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(), Math.max(drop, subscriptshift)];
//
// q is the space currently between the super- and subscripts.
// If it is less than 3 rule thicknesses,
// increase the subscript offset to make the space 3 rule thicknesses
// If the bottom of the superscript is below 4/5 of the x-height
// raise both the super- and subscripts by the difference
// (make the bottom of the superscript be at 4/5 the x-height, and the
// subscript 3 rule thickness below that).
//
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;
}
}
//
// Make sure the shifts are at least the minimum amounts and
// return the shifts and the space between the scripts
//
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;
}
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 CommonMtd wrapper mixin for the MmlMtd object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {CommonMtable} from '../../common/Wrappers/mtable.js';
import {CommonMtr} from '../../common/Wrappers/mtr.js';
/*****************************************************************/
/**
* The CommonMtd interface
*/
export interface CommonMtd extends AnyWrapper {
}
/**
* Shorthand for the CommonMtd constructor
*/
export type MtdConstructor = Constructor<CommonMtd>;
/*****************************************************************/
/**
* The CommonMtd wrapper mixin for the MmlMtd object
*
* @template T The Wrapper class constructor type
*/
export function CommonMtdMixin<T extends WrapperConstructor>(Base: T): MtdConstructor & T {
return class extends Base {
/**
* @override
*/
get fixesPWidth() {
return false;
}
/**
* @override
*/
public invalidateBBox() {
this.bboxComputed = false;
}
/**
* @override
*/
public getWrapWidth(_j: number) {
const table = this.parent.parent as any as CommonMtable<AnyWrapper, CommonMtr<AnyWrapper>>;
const row = this.parent as CommonMtr<AnyWrapper>;
const i = this.node.childPosition() - (row.labeled ? 1 : 0);
return (typeof(table.cWidths[i]) === 'number' ? table.cWidths[i] : table.getTableData().W[i]) as number;
}
/**
* @override
*/
public getChildAlign(_i: number) {
return this.node.attributes.get('columnalign') as string;
}
};
}

View File

@@ -0,0 +1,86 @@
/*************************************************************
*
* Copyright (c) 2019-2022 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 CommonMtext wrapper mixin for the MmlMtext object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
/*****************************************************************/
/**
* The CommonMtext interface
*/
export interface CommonMtext extends AnyWrapper {
}
/**
* Shorthand for the CommonMtext constructor
*/
export type MtextConstructor = Constructor<CommonMtext>;
/*****************************************************************/
/**
* The CommonMtext wrapper mixin for the MmlMtext object
*
* @template T The Wrapper class constructor type
*/
export function CommonMtextMixin<T extends WrapperConstructor>(Base: T): MtextConstructor & T {
return class extends Base {
/**
* The font-family, weight, and style to use for the variants when mtextInheritFont
* is true or mtextFont is specified. If not in this list, then the font's
* getCssFont() is called. When the font family is not specified (as in these four),
* the inherited or specified font is used.
*/
public static INHERITFONTS = {
normal: ['', false, false],
bold: ['', false, true],
italic: ['', true, false],
'bold-italic': ['', true, true]
};
/**
* @override
*/
protected getVariant() {
const options = this.jax.options;
const data = this.jax.math.outputData;
//
// If the font is to be inherited from the surrounding text, check the mathvariant
// and see if it allows for inheritance. If so, set the variant appropriately,
// otherwise get the usual variant.
//
const merror = ((!!data.merrorFamily || !!options.merrorFont) && this.node.Parent.isKind('merror'));
if (!!data.mtextFamily || !!options.mtextFont || merror) {
const variant = this.node.attributes.get('mathvariant') as string;
const font = (this.constructor as any).INHERITFONTS[variant] || this.jax.font.getCssFont(variant);
const family = font[0] || (merror ? data.merrorFamily || options.merrorFont :
data.mtextFamily || options.mtextFont);
this.variant = this.explicitVariant(family, font[2] ? 'bold' : '', font[1] ? 'italic' : '');
return;
}
super.getVariant();
}
};
}

View File

@@ -0,0 +1,277 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CcommonMtr wrapper mixin for the MmlMtr object
* and CommonMlabeledtr wrapper mixin for MmlMlabeledtr
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {CommonMo} from './mo.js';
import {BBox} from '../../../util/BBox.js';
import {DIRECTION} from '../FontData.js';
/*****************************************************************/
/**
* The CommonMtr interface
*
* @template C The class for table cells
*/
export interface CommonMtr<C extends AnyWrapper> extends AnyWrapper {
/**
* The number of mtd's in the mtr
*/
readonly numCells: number;
/**
* True if this is a labeled row
*/
readonly labeled: boolean;
/**
* The child nodes that are part of the table (no label node)
*/
readonly tableCells: C[];
/**
* @override;
*/
childNodes: C[];
/**
* @param {number} i The index of the child to get (skipping labels)
* @return {C} The ith child node wrapper
*/
getChild(i: number): C;
/**
* @return {BBox[]} An array of the bounding boxes for the mtd's in the row
*/
getChildBBoxes(): BBox[];
/**
* Handle vertical stretching of cells to match height of
* other cells in the row.
*
* @param {number[]=} HD The total height and depth for the row [H, D]
*
* If this isn't specified, the maximum height and depth is computed.
*/
stretchChildren(HD?: number[]): void;
}
/**
* Shorthand for the CommonMtr constructor
*
* @template C The class for table cells
*/
export type MtrConstructor<C extends AnyWrapper> = Constructor<CommonMtr<C>>;
/*****************************************************************/
/**
* The CommonMtr wrapper for the MmlMtr object
*
* @template C The class for table cells
* @template T The Wrapper class constructor type
*/
export function CommonMtrMixin<
C extends AnyWrapper,
T extends WrapperConstructor
>(Base: T): MtrConstructor<C> & T {
return class extends Base {
/**
* @override
*/
get fixesPWidth() {
return false;
}
/**
* @return {number} The number of mtd's in the mtr
*/
get numCells(): number {
return this.childNodes.length;
}
/**
* @return {boolean} True if this is a labeled row
*/
get labeled(): boolean {
return false;
}
/**
* @return {C[]} The child nodes that are part of the table (no label node)
*/
get tableCells(): C[] {
return this.childNodes;
}
/**
* @param {number} i The index of the child to get (skipping labels)
* @return {C} The ith child node wrapper
*/
public getChild(i: number): C {
return this.childNodes[i];
}
/**
* @return {BBox[]} An array of the bounding boxes for the mtd's in the row
*/
public getChildBBoxes(): BBox[] {
return this.childNodes.map(cell => cell.getBBox());
}
/**
* Handle vertical stretching of cells to match height of
* other cells in the row.
*
* @param {number[]} HD The total height and depth for the row [H, D]
*
* If this isn't specified, the maximum height and depth is computed.
*/
public stretchChildren(HD: number[] = null) {
let stretchy: AnyWrapper[] = [];
let children = (this.labeled ? this.childNodes.slice(1) : this.childNodes);
//
// Locate and count the stretchy children
//
for (const mtd of children) {
const child = mtd.childNodes[0];
if (child.canStretch(DIRECTION.Vertical)) {
stretchy.push(child);
}
}
let count = stretchy.length;
let nodeCount = this.childNodes.length;
if (count && nodeCount > 1) {
if (HD === null) {
let H = 0, D = 0;
//
// If all the children are stretchy, find the largest one,
// otherwise, find the height and depth of the non-stretchy
// children.
//
let all = (count > 1 && count === nodeCount);
for (const mtd of children) {
const child = mtd.childNodes[0];
const noStretch = (child.stretch.dir === DIRECTION.None);
if (all || noStretch) {
const {h, d} = child.getBBox(noStretch);
if (h > H) {
H = h;
}
if (d > D) {
D = d;
}
}
}
HD = [H, D];
}
//
// Stretch the stretchable children
//
for (const child of stretchy) {
(child.coreMO() as CommonMo).getStretchedVariant(HD);
}
}
}
};
}
/*****************************************************************/
/**
* The CommonMlabeledtr interface
*
* @template C The class for table cells
*/
export interface CommonMlabeledtr<C extends AnyWrapper> extends CommonMtr<C> {
}
/**
* Shorthand for the CommonMlabeledtr constructor
*
* @template C The class for table cells
*/
export type MlabeledtrConstructor<C extends AnyWrapper> = Constructor<CommonMlabeledtr<C>>;
/*****************************************************************/
/**
* The CommonMlabeledtr wrapper mixin for the MmlMlabeledtr object
*
* @template C The class for table cells
* @template T The Wrapper class constructor type
*/
export function CommonMlabeledtrMixin<
C extends AnyWrapper,
T extends MtrConstructor<C>
>(Base: T): MlabeledtrConstructor<C> & T {
return class extends Base {
/**
* @override
*/
get numCells() {
//
// Don't include the label mtd
//
return Math.max(0, this.childNodes.length - 1);
}
/**
* @override
*/
get labeled() {
return true;
}
/**
* @override
*/
get tableCells() {
return this.childNodes.slice(1) as C[];
}
/**
* @override
*/
public getChild(i: number) {
return this.childNodes[i + 1] as C;
}
/**
* @override
*/
public getChildBBoxes() {
//
// Don't include the label mtd
//
return this.childNodes.slice(1).map(cell => cell.getBBox());
}
};
}

View File

@@ -0,0 +1,287 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonMunderover wrapper mixin for the MmlMunderover object
* and the special cases CommonMunder and CommonMsup
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, Constructor} from '../Wrapper.js';
import {CommonScriptbase, ScriptbaseConstructor} from './scriptbase.js';
import {MmlMunderover, MmlMunder, MmlMover} from '../../../core/MmlTree/MmlNodes/munderover.js';
import {BBox} from '../../../util/BBox.js';
/*****************************************************************/
/**
* The CommonMunder interface
*
* @template W The child-node Wrapper class
*/
export interface CommonMunder<W extends AnyWrapper> extends CommonScriptbase<W> {
}
/**
* Shorthand for the CommonMunder constructor
*
* @template W The child-node Wrapper class
*/
export type MunderConstructor<W extends AnyWrapper> = Constructor<CommonMunder<W>>;
/*****************************************************************/
/**
* The CommonMunder wrapper mixin for the MmlMunder object
*
* @template W The child-node Wrapper class
* @template T The Wrapper class constructor type
*/
export function CommonMunderMixin<
W extends AnyWrapper,
T extends ScriptbaseConstructor<W>
>(Base: T): MunderConstructor<W> & T {
return class extends Base {
/**
* @override
*/
public get scriptChild() {
return this.childNodes[(this.node as MmlMunder).under];
}
/**
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
this.stretchChildren();
}
/**
* @override
*/
public computeBBox(bbox: BBox, recompute: boolean = false) {
if (this.hasMovableLimits()) {
super.computeBBox(bbox, recompute);
return;
}
bbox.empty();
const basebox = this.baseChild.getOuterBBox();
const underbox = this.scriptChild.getOuterBBox();
const v = this.getUnderKV(basebox, underbox)[1];
const delta = (this.isLineBelow ? 0 : this.getDelta(true));
const [bw, uw] = this.getDeltaW([basebox, underbox], [0, -delta]);
bbox.combine(basebox, bw, 0);
bbox.combine(underbox, uw, v);
bbox.d += this.font.params.big_op_spacing5;
bbox.clean();
this.setChildPWidths(recompute);
}
};
}
/*****************************************************************/
/**
* The CommonMover interface
*
* @template W The child-node Wrapper class
*/
export interface CommonMover<W extends AnyWrapper> extends CommonScriptbase<W> {
}
/**
* Shorthand for the CommonMover constructor
*
* @template W The child-node Wrapper class
*/
export type MoverConstructor<W extends AnyWrapper> = Constructor<CommonMover<W>>;
/*****************************************************************/
/**
* The CommonMover wrapper mixin for the MmlMover object
*
* @template W The child-node Wrapper class
* @template T The Wrapper class constructor type
*/
export function CommonMoverMixin<
W extends AnyWrapper,
T extends ScriptbaseConstructor<W>
>(Base: T): MoverConstructor<W> & T {
return class extends Base {
/**
* @override
*/
public get scriptChild() {
return this.childNodes[(this.node as MmlMover).over];
}
/**
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
this.stretchChildren();
}
/**
* @override
*/
public computeBBox(bbox: BBox) {
if (this.hasMovableLimits()) {
super.computeBBox(bbox);
return;
}
bbox.empty();
const basebox = this.baseChild.getOuterBBox();
const overbox = this.scriptChild.getOuterBBox();
if (this.node.attributes.get('accent')) {
basebox.h = Math.max(basebox.h, this.font.params.x_height * basebox.scale);
}
const u = this.getOverKU(basebox, overbox)[1];
const delta = (this.isLineAbove ? 0 : this.getDelta());
const [bw, ow] = this.getDeltaW([basebox, overbox], [0, delta]);
bbox.combine(basebox, bw, 0);
bbox.combine(overbox, ow, u);
bbox.h += this.font.params.big_op_spacing5;
bbox.clean();
}
};
}
/*****************************************************************/
/**
* The CommonMunderover interface
*
* @template W The child-node Wrapper class
*/
export interface CommonMunderover<W extends AnyWrapper> extends CommonScriptbase<W> {
/*
* The wrapped under node
*/
readonly underChild: W;
/*
* The wrapped overder node
*/
readonly overChild: W;
}
/**
* Shorthand for the CommonMunderover constructor
*
* @template W The child-node Wrapper class
*/
export type MunderoverConstructor<W extends AnyWrapper> = Constructor<CommonMunderover<W>>;
/*****************************************************************/
/*
* The CommonMunderover wrapper for the MmlMunderover object
*
* @template W The child-node Wrapper class
* @template T The Wrapper class constructor type
*/
export function CommonMunderoverMixin<
W extends AnyWrapper,
T extends ScriptbaseConstructor<W>
>(Base: T): MunderoverConstructor<W> & T {
return class extends Base {
/*
* @return {W} The wrapped under node
*/
public get underChild() {
return this.childNodes[(this.node as MmlMunderover).under];
}
/*
* @return {W} 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
* @constructor
*/
constructor(...args: any[]) {
super(...args);
this.stretchChildren();
}
/**
* @override
*/
public computeBBox(bbox: BBox) {
if (this.hasMovableLimits()) {
super.computeBBox(bbox);
return;
}
bbox.empty();
const overbox = this.overChild.getOuterBBox();
const basebox = this.baseChild.getOuterBBox();
const underbox = this.underChild.getOuterBBox();
if (this.node.attributes.get('accent')) {
basebox.h = Math.max(basebox.h, this.font.params.x_height * basebox.scale);
}
const u = this.getOverKU(basebox, overbox)[1];
const v = this.getUnderKV(basebox, underbox)[1];
const delta = this.getDelta();
const [bw, uw, ow] = this.getDeltaW([basebox, underbox, overbox],
[0, this.isLineBelow ? 0 : -delta, this.isLineAbove ? 0 : delta]);
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();
}
};
}

View File

@@ -0,0 +1,709 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 mixin for CommonMsubsup, CommonMunderover
* and their relatives. (Since munderover can become msubsup
* when movablelimits is set, munderover needs to be able to
* do the same thing as msubsup in some cases.)
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor, AnyWrapperClass} from '../Wrapper.js';
import {CommonMo} from './mo.js';
import {CommonMunderover} from './munderover.js';
import {TEXCLASS} from '../../../core/MmlTree/MmlNode.js';
import {MmlMsubsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
import {MmlMo} from '../../../core/MmlTree/MmlNodes/mo.js';
import {BBox} from '../../../util/BBox.js';
import {DIRECTION} from '../FontData.js';
/*****************************************************************/
/**
* The CommonScriptbase interface
*
* @template W The child-node Wrapper class
*/
export interface CommonScriptbase<W extends AnyWrapper> extends AnyWrapper {
/**
* The core mi or mo of the base (or the base itself if there isn't one)
*/
readonly baseCore: W;
/**
* The base element's wrapper
*/
readonly baseChild: W;
/**
* The relative scaling of the base compared to the munderover/msubsup
*/
readonly baseScale: number;
/**
* The italic correction of the base (if any)
*/
readonly baseIc: number;
/**
* True if base italic correction should be removed (msub and msubsup or mathaccents)
*/
readonly baseRemoveIc: boolean;
/**
* True if the base is a single character
*/
readonly baseIsChar: boolean;
/**
* True if the base has an accent under or over
*/
readonly baseHasAccentOver: boolean;
readonly baseHasAccentUnder: boolean;
/**
* True if this is an overline or underline
*/
readonly isLineAbove: boolean;
readonly isLineBelow: boolean;
/**
* True if this is an msup with script that is a math accent
*/
readonly isMathAccent: boolean;
/**
* The script element's wrapper (overridden in subclasses)
*/
readonly scriptChild: W;
/***************************************************************************/
/*
* Methods for information about the core element for the base
*/
/**
* @return {W} The wrapper for the base core mi or mo (or whatever)
*/
getBaseCore(): W;
/**
* @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;
/**
* An adjusted italic correction (for slightly better results)
*/
getAdjustedIc(): number;
/**
* @return {boolean} True if the base is an mi, mn, or mo (not a largeop) consisting of
* a single unstretched character
*/
isCharBase(): boolean;
/**
* Determine if the under- and overscripts are under- or overlines.
*/
checkLineAccents(): void;
/**
* @param {W} script The script node to check for being a line
*/
isLineAccent(script: W): boolean;
/***************************************************************************/
/*
* Methods for sub-sup nodes
*/
/**
* @return {number} The base child's width without the base italic correction (if not needed)
*/
getBaseWidth(): number;
/**
* Get the shift for the script (implemented in subclasses)
*
* @return {number[]} The horizontal and vertical offsets for the script
*/
getOffset(): number[];
/**
* @param {number} n The value to use if the base isn't a (non-large-op, unstretched) char
* @return {number} Either n or 0
*/
baseCharZero(n: number): number;
/**
* Get the shift for a subscript (TeXBook Appendix G 18ab)
*
* @return {number} The vertical offset for the script
*/
getV(): number;
/**
* Get the shift for a superscript (TeXBook Appendix G 18acd)
*
* @return {number} The vertical offset for the script
*/
getU(): number;
/***************************************************************************/
/*
* Methods for under-over nodes
*/
/**
* @return {boolean} True if the base has movablelimits (needed by munderover)
*/
hasMovableLimits(): boolean;
/**
* 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 {number[]} The separation between their boxes, and the offset of the overscript
*/
getOverKU(basebox: BBox, overbox: BBox): number[];
/**
* 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 {number[]} The separation between their boxes, and the offset of the underscript
*/
getUnderKV(basebox: BBox, underbox: BBox): number[];
/**
* @param {BBox[]} boxes The bounding boxes whose offsets are to be computed
* @param {number[]=} delta The initial x offsets of the boxes
* @return {number[]} The actual offsets needed to center the boxes in the stack
*/
getDeltaW(boxes: BBox[], delta?: number[]): number[];
/**
* @param {boolean=} noskew Whether to ignore the skew amount
* @return {number} The offset for under and over
*/
getDelta(noskew?: boolean): number;
/**
* Handle horizontal stretching of children to match greatest width
* of all children
*/
stretchChildren(): void;
}
export interface CommonScriptbaseClass extends AnyWrapperClass {
/**
* Set to true for munderover/munder/mover/msup (Appendix G 13)
*/
useIC: boolean;
}
/**
* Shorthand for the CommonScriptbase constructor
*
* @template W The child-node Wrapper class
*/
export type ScriptbaseConstructor<W extends AnyWrapper> = Constructor<CommonScriptbase<W>>;
/*****************************************************************/
/**
* A base class for msup/msub/msubsup and munder/mover/munderover
* wrapper mixin implementations
*
* @template W The child-node Wrapper class
* @template T The Wrapper class constructor type
*/
export function CommonScriptbaseMixin<
W extends AnyWrapper,
T extends WrapperConstructor
>(Base: T): ScriptbaseConstructor<W> & T {
return class extends Base {
/**
* Set to false for msubsup/msub (Appendix G 13)
*/
public static useIC: boolean = true;
/**
* The core mi or mo of the base (or the base itself if there isn't one)
*/
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 base italic correction should be removed (msub and msubsup or mathaccents)
*/
public baseRemoveIc: boolean = false;
/**
* True if the base is a single character
*/
public baseIsChar: boolean = false;
/**
* True if the base has an accent under or over
*/
public baseHasAccentOver: boolean = null;
public baseHasAccentUnder: boolean = null;
/**
* True if this is an overline or underline
*/
public isLineAbove: boolean = false;
public isLineBelow: boolean = false;
/**
* True if this is an msup with script that is a math accent
*/
public isMathAccent: boolean = false;
/**
* @return {W} The base element's wrapper
*/
public get baseChild(): W {
return this.childNodes[(this.node as MmlMsubsup).base];
}
/**
* @return {W} The script element's wrapper (overridden in subclasses)
*/
public get scriptChild(): W {
return this.childNodes[1];
}
/**
* @override
*/
constructor(...args: any[]) {
super(...args);
//
// Find the base core
//
const core = this.baseCore = this.getBaseCore();
if (!core) return;
//
// Get information about the base element
//
this.setBaseAccentsFor(core);
this.baseScale = this.getBaseScale();
this.baseIc = this.getBaseIc();
this.baseIsChar = this.isCharBase();
//
// Determine if we are setting a mathaccent
//
this.isMathAccent = this.baseIsChar &&
(this.scriptChild && !!this.scriptChild.coreMO().node.getProperty('mathaccent')) as boolean;
//
// Check for overline/underline accents
//
this.checkLineAccents();
//
// Check if the base is a mi or mo that needs italic correction removed
//
this.baseRemoveIc = !this.isLineAbove && !this.isLineBelow &&
(!(this.constructor as CommonScriptbaseClass).useIC || this.isMathAccent);
}
/***************************************************************************/
/*
* 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.texClass !== TEXCLASS.VCENTER) ||
core.node.isKind('mstyle') || core.node.isKind('mpadded') ||
core.node.isKind('mphantom') || core.node.isKind('semantics'))) ||
(core.node.isKind('munderover') && core.isMathAccent))) {
this.setBaseAccentsFor(core);
core = core.childNodes[0];
}
if (!core) {
this.baseHasAccentOver = this.baseHasAccentUnder = false;
}
return core || this.childNodes[0];
}
/**
* @param {W} core The element to check for accents
*/
public setBaseAccentsFor(core: W) {
if (core.node.isKind('munderover')) {
if (this.baseHasAccentOver === null) {
this.baseHasAccentOver = !!core.node.attributes.get('accent');
}
if (this.baseHasAccentUnder === null) {
this.baseHasAccentUnder = !!core.node.attributes.get('accentunder');
}
}
}
/**
* @return {W} The base fence item or null
*/
public getSemanticBase(): W {
let fence = this.node.attributes.getExplicit('data-semantic-fencepointer') as string;
return this.getBaseFence(this.baseChild, fence);
}
/**
* 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 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 scaling factor for the base core relative to the munderover/msubsup
*/
public getBaseScale(): number {
let child = this.baseCore as any;
let scale = 1;
while (child && child !== this) {
const bbox = child.getOuterBBox();
scale *= bbox.rscale;
child = child.parent;
}
return scale;
}
/**
* The base's italic correction (properly scaled)
*/
public getBaseIc(): number {
return this.baseCore.getOuterBBox().ic * this.baseScale;
}
/**
* An adjusted italic correction (for slightly better results)
*/
public getAdjustedIc(): number {
const bbox = this.baseCore.getOuterBBox();
return (bbox.ic ? 1.05 * bbox.ic + .05 : 0) * this.baseScale;
}
/**
* @return {boolean} True if the base is an mi, mn, or mo consisting of a single character
*/
public isCharBase(): boolean {
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);
}
/**
* Determine if the under- and overscripts are under- or overlines.
*/
public checkLineAccents() {
if (!this.node.isKind('munderover')) return;
if (this.node.isKind('mover')) {
this.isLineAbove = this.isLineAccent(this.scriptChild);
} else if (this.node.isKind('munder')) {
this.isLineBelow = this.isLineAccent(this.scriptChild);
} else {
const mml = this as unknown as CommonMunderover<W>;
this.isLineAbove = this.isLineAccent(mml.overChild);
this.isLineBelow = this.isLineAccent(mml.underChild);
}
}
/**
* @param {W} script The script node to check for being a line
* @return {boolean} True if the script is U+2015
*/
public isLineAccent(script: W): boolean {
const node = script.coreMO().node;
return (node.isToken && (node as MmlMo).getText() === '\u2015');
}
/***************************************************************************/
/*
* Methods for sub-sup nodes
*/
/**
* @return {number} The base child's width without the base italic correction (if not needed)
*/
public getBaseWidth(): number {
const bbox = this.baseChild.getOuterBBox();
return bbox.w * bbox.rscale - (this.baseRemoveIc ? this.baseIc : 0) + this.font.params.extra_ic;
}
/**
* 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 w = this.getBaseWidth();
const [x, y] = this.getOffset();
bbox.append(this.baseChild.getOuterBBox());
bbox.combine(this.scriptChild.getOuterBBox(), w + x, y);
bbox.w += this.font.params.scriptspace;
bbox.clean();
this.setChildPWidths(recompute);
}
/**
* Get the shift for the script (implemented in subclasses)
*
* @return {[number, number]} The horizontal and vertical offsets for the script
*/
public getOffset(): [number, number] {
return [0, 0];
}
/**
* @param {number} n The value to use if the base isn't a (non-large-op, unstretched) char
* @return {number} Either n or 0
*/
public baseCharZero(n: number): number {
const largeop = !!this.baseCore.node.attributes.get('largeop');
const scale = this.baseScale;
return (this.baseIsChar && !largeop && scale === 1 ? 0 : n);
}
/**
* Get the shift for a subscript (TeXBook Appendix G 18ab)
*
* @return {number} The vertical offset for the script
*/
public getV(): number {
const bbox = this.baseCore.getOuterBBox();
const sbox = this.scriptChild.getOuterBBox();
const tex = this.font.params;
const subscriptshift = this.length2em(this.node.attributes.get('subscriptshift'), tex.sub1);
return Math.max(
this.baseCharZero(bbox.d * this.baseScale + 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)
*
* @return {number} The vertical offset for the script
*/
public getU(): number {
const bbox = this.baseCore.getOuterBBox();
const sbox = this.scriptChild.getOuterBBox();
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);
return Math.max(
this.baseCharZero(bbox.h * this.baseScale - 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)
*/
public hasMovableLimits(): boolean {
const display = this.node.attributes.get('displaystyle');
const mo = this.baseChild.coreMO().node;
return (!display && !!mo.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 {[number, number]} The separation between their boxes, and the offset of the overscript
*/
public getOverKU(basebox: BBox, overbox: BBox): [number, number] {
const accent = this.node.attributes.get('accent') as boolean;
const tex = this.font.params;
const d = overbox.d * overbox.rscale;
const t = tex.rule_thickness * tex.separation_factor;
const delta = (this.baseHasAccentOver ? t : 0);
const T = (this.isLineAbove ? 3 * tex.rule_thickness : t);
const k = (accent ? T : Math.max(tex.big_op_spacing1, tex.big_op_spacing3 - Math.max(0, d))) - delta;
return [k, basebox.h * basebox.rscale + 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 {[number, number]} The separation between their boxes, and the offset of the underscript
*/
public getUnderKV(basebox: BBox, underbox: BBox): [number, number] {
const accent = this.node.attributes.get('accentunder') as boolean;
const tex = this.font.params;
const h = underbox.h * underbox.rscale;
const t = tex.rule_thickness * tex.separation_factor;
const delta = (this.baseHasAccentUnder ? t : 0);
const T = (this.isLineBelow ? 3 * tex.rule_thickness : t);
const k = (accent ? T : Math.max(tex.big_op_spacing2, tex.big_op_spacing4 - h)) - delta;
return [k, -(basebox.d * basebox.rscale + k + h)];
}
/**
* @param {BBox[]} boxes The bounding boxes whose offsets are to be computed
* @param {number[]=} delta The initial x offsets of the boxes
* @return {number[]} The actual offsets needed to center the boxes in the stack
*/
public getDeltaW(boxes: BBox[], delta: number[] = [0, 0, 0]): number[] {
const align = this.node.attributes.get('align');
const widths = boxes.map(box => box.w * box.rscale);
widths[0] -= (this.baseRemoveIc && !this.baseCore.node.attributes.get('largeop') ? this.baseIc : 0);
const w = Math.max(...widths);
const dw = [] as number[];
let m = 0;
for (const i of widths.keys()) {
dw[i] = (align === 'center' ? (w - widths[i]) / 2 :
align === 'right' ? w - widths[i] : 0) + delta[i];
if (dw[i] < m) {
m = -dw[i];
}
}
if (m) {
for (const i of dw.keys()) {
dw[i] += m;
}
}
[1, 2].map(i => dw[i] += (boxes[i] ? boxes[i].dx * boxes[0].scale : 0));
return dw;
}
/**
* @param {boolean=} noskew Whether to ignore the skew amount
* @return {number} The offset for under and over
*/
public getDelta(noskew: boolean = false): number {
const accent = this.node.attributes.get('accent');
const {sk, ic} = this.baseCore.getOuterBBox();
return ((accent && !noskew ? sk : 0) + this.font.skewIcFactor * ic) * this.baseScale;
}
/**
* Handle horizontal stretching of children to match greatest width
* of all children
*/
public stretchChildren() {
let stretchy: AnyWrapper[] = [];
//
// Locate and count the stretchy children
//
for (const child of this.childNodes) {
if (child.canStretch(DIRECTION.Horizontal)) {
stretchy.push(child);
}
}
let count = stretchy.length;
let nodeCount = this.childNodes.length;
if (count && nodeCount > 1) {
let W = 0;
//
// If all the children are stretchy, find the largest one,
// otherwise, find the width of the non-stretchy children.
//
let all = (count > 1 && count === nodeCount);
for (const child of this.childNodes) {
const noStretch = (child.stretch.dir === DIRECTION.None);
if (all || noStretch) {
const {w, rscale} = child.getOuterBBox(noStretch);
if (w * rscale > W) W = w * rscale;
}
}
//
// Stretch the stretchable children
//
for (const child of stretchy) {
(child.coreMO() as CommonMo).getStretchedVariant([W / child.bbox.rscale]);
}
}
}
};
}

View File

@@ -0,0 +1,62 @@
/*************************************************************
*
* Copyright (c) 2017-2022 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 CommonSemantics wrapper mixin for the MmlSemantics object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
import {BBox} from '../../../util/BBox.js';
/*****************************************************************/
/**
* The CommonSemantics interface
*/
export interface CommonSemantics extends AnyWrapper {
}
/**
* Shorthand for the CommonSemantics constructor
*/
export type SemanticsConstructor = Constructor<CommonSemantics>;
/*****************************************************************/
/**
* The CommonSemantics wrapper mixin for the MmlSemantics object
*
* @template T The Wrapper class constructor type
*/
export function CommonSemanticsMixin<T extends WrapperConstructor>(Base: T): SemanticsConstructor & T {
return class extends Base {
/**
* @override
*/
public computeBBox(bbox: BBox, _recompute: boolean = false) {
if (this.childNodes.length) {
const {w, h, d} = this.childNodes[0].getBBox();
bbox.w = w;
bbox.h = h;
bbox.d = d;
}
}
};
}