Skip to content

Commit

Permalink
Merge pull request #660 from mathjax/issue2571
Browse files Browse the repository at this point in the history
Fix issues with placement of menclose notations. (mathjax/MathJax#2571)
  • Loading branch information
dpvc authored Apr 8, 2021
2 parents 3d59e72 + ba17899 commit cd12327
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 58 deletions.
9 changes: 6 additions & 3 deletions ts/output/chtml/Notation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ export type DEFPAIR<N, T, D> = Notation.DefPair<CHTMLmenclose<N, T, D>, N>;
export const RenderElement = function<N, T, D>(name: string, offset: string = ''): RENDERER<N, T, D> {
return ((node, _child) => {
const shape = node.adjustBorder(node.html('mjx-' + name));
if (offset && node.thickness !== Notation.THICKNESS) {
const transform = 'translate' + offset + '(' + node.em(node.thickness / 2) + ')';
node.adaptor.setStyle(shape, 'transform', transform);
if (offset) {
const d = node.getOffset(offset);
if (node.thickness !== Notation.THICKNESS || d) {
const transform = `translate${offset}(${node.em(node.thickness / 2 - d)})`;
node.adaptor.setStyle(shape, 'transform', transform);
}
}
node.adaptor.append(node.chtml, shape);
}) as Notation.Renderer<CHTMLmenclose<N, T, D>, N>;
Expand Down
20 changes: 18 additions & 2 deletions ts/output/chtml/Wrappers/menclose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,11 @@ CommonMencloseMixin<
* @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
* @return {N} The newly created 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 = false): N {
public arrow(w: number, a: number, double: boolean, offset: string = '', dist: number = 0): N {
const W = this.getBBox().w;
const style = {width: this.em(w)} as OptionList;
if (W !== w) {
Expand All @@ -373,6 +375,7 @@ CommonMencloseMixin<
this.adaptor.setAttribute(arrow, 'double', 'true');
}
this.adjustArrow(arrow, double);
this.moveArrow(arrow, offset, dist);
return arrow;
}

Expand Down Expand Up @@ -422,6 +425,19 @@ CommonMencloseMixin<
}
}

/**
* @param {N} arrow The arrow whose position is to be adjusted
* @param {string} offset The direction to move the arrow
* @param {number} d The distance to translate in that direction
*/
protected moveArrow(arrow: N, offset: string, d: number) {
if (!d) return;
const transform = this.adaptor.getStyle(arrow, 'transform');
this.adaptor.setStyle(
arrow, 'transform', `translate${offset}(${this.em(-d)})${(transform ? ' ' + transform : '')}`
);
}

/********************************************************/

/**
Expand Down
30 changes: 18 additions & 12 deletions ts/output/common/Notation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,19 @@ export const SOLID = THICKNESS + 'em solid'; // a solid border
*/
export type Menclose = CommonMenclose<any, any, any>;

/**
* Top, right, bottom, left padding data
*/
export type PaddingData = [number, number, number, number];

/**
* The functions used for notation definitions
*
* @templare N The DOM node class
*/
export type Renderer<W extends AnyWrapper, N> = (node: W, child: N) => void;
export type BBoxExtender<W extends AnyWrapper> = (node: W) => number[];
export type BBoxBorder<W extends AnyWrapper> = (node: W) => number[];
export type BBoxExtender<W extends AnyWrapper> = (node: W) => PaddingData;
export type BBoxBorder<W extends AnyWrapper> = (node: W) => PaddingData;
export type Initializer<W extends AnyWrapper> = (node: W) => void;

/**
Expand Down Expand Up @@ -112,7 +117,7 @@ export const arrowHead = (node: Menclose) => {
/**
* Adjust short bbox for tall arrow heads
*/
export const arrowBBoxHD = (node: Menclose, TRBL: number[]) => {
export const arrowBBoxHD = (node: Menclose, TRBL: PaddingData) => {
if (node.childNodes[0]) {
const {h, d} = node.childNodes[0].getBBox();
TRBL[0] = TRBL[2] = Math.max(0, node.thickness * node.arrowhead.y - (h + d) / 2);
Expand All @@ -123,7 +128,7 @@ export const arrowBBoxHD = (node: Menclose, TRBL: number[]) => {
/**
* Adjust thin bbox for wide arrow heads
*/
export const arrowBBoxW = (node: Menclose, TRBL: number[]) => {
export const arrowBBoxW = (node: Menclose, TRBL: PaddingData) => {
if (node.childNodes[0]) {
const {w} = node.childNodes[0].getBBox();
TRBL[1] = TRBL[3] = Math.max(0, node.thickness * node.arrowhead.y - w / 2);
Expand Down Expand Up @@ -193,15 +198,15 @@ export const CommonBorder = function<W extends Menclose, N>(render: Renderer<W,
// Indicate the extra space on the given side
//
bbox: (node) => {
const bbox = [0, 0, 0, 0];
const bbox = [0, 0, 0, 0] as PaddingData;
bbox[i] = node.thickness + node.padding;
return bbox;
},
//
// Indicate the border on the given side
//
border: (node) => {
const bbox = [0, 0, 0, 0];
const bbox = [0, 0, 0, 0] as PaddingData;
bbox[i] = node.thickness;
return bbox;
}
Expand Down Expand Up @@ -235,15 +240,15 @@ export const CommonBorder2 = function<W extends Menclose, N>(render: Renderer<W,
//
bbox: (node) => {
const t = node.thickness + node.padding;
const bbox = [0, 0, 0, 0];
const bbox = [0, 0, 0, 0] as PaddingData;
bbox[i1] = bbox[i2] = t;
return bbox;
},
//
// Indicate the border on the two sides
//
border: (node) => {
const bbox = [0, 0, 0, 0];
const bbox = [0, 0, 0, 0] as PaddingData;
bbox[i1] = bbox[i2] = node.thickness;
return bbox;
},
Expand Down Expand Up @@ -301,8 +306,8 @@ export const CommonDiagonalArrow = function<W extends Menclose, N>(render: Rende
// the arrow from them and the other arrow data
//
renderer: (node, _child) => {
const {a, W} = node.arrowData();
const arrow = node.arrow(W, c * (a - pi), double);
const [a, W] = node.arrowAW();
const arrow = node.arrow(W, c * (a - pi), double);
render(node, arrow);
},
//
Expand Down Expand Up @@ -342,8 +347,9 @@ export const CommonArrow = function<W extends Menclose, N>(render: Renderer<W, N
//
renderer: (node, _child) => {
const {w, h, d} = node.getBBox();
const W = (isVertical ? h + d : w);
const arrow = node.arrow(W, angle, double);
const [W, offset] = (isVertical ? [h + d, 'X'] : [w, 'Y']);
const dd = node.getOffset(offset);
const arrow = node.arrow(W, angle, double, offset, dd);
render(node, arrow);
},
//
Expand Down
101 changes: 76 additions & 25 deletions ts/output/common/Wrappers/menclose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export interface CommonMenclose<W extends AnyWrapper, S extends CommonMsqrt, N>
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
*/
Expand All @@ -76,46 +81,63 @@ export interface CommonMenclose<W extends AnyWrapper, S extends CommonMsqrt, N>
initializeNotations(): void;

/**
* @return {number[]} Array of the maximum extra space from the notations along each side
* @return {Notation.PaddingData} Array of the maximum extra space from the notations along each side
*/
getBBoxExtenders(): number[];
getBBoxExtenders(): Notation.PaddingData;

/**
* @return {number[]} Array of padding (i.e., BBox minus border) along each side
* @return {Notation.PaddingData} Array of padding (i.e., BBox minus border) along each side
*/
getPadding(): number[];
getPadding(): Notation.PaddingData;

/**
* Each entry in X gets replaced by the corresponding one in Y if it is larger
*
* @param {number[]} X An array of numbers
* @param {number[]} Y An array of numbers that replace smaller ones in X
* @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
*/
maximizeEntries(X: number[], Y: number[]): void;
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[];
getArgMod(w: number, h: number): [number, number];

/**
* Create an arrow using an svg element
* 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 {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): N;
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
Expand Down Expand Up @@ -201,6 +223,11 @@ export function CommonMencloseMixin<
*/
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
Expand All @@ -211,6 +238,7 @@ export function CommonMencloseMixin<
this.getNotations();
this.removeRedundantNotations();
this.initializeNotations();
this.TRBL = this.getBBoxExtenders();
}

/**
Expand Down Expand Up @@ -287,7 +315,7 @@ export function CommonMencloseMixin<
//
// Combine the BBox from the child and add the extenders
//
let [T, R, B, L] = this.getBBoxExtenders();
let [T, R, B, L] = this.TRBL;
const child = this.childNodes[0].getBBox();
bbox.combine(child, L, 0);
bbox.h += T;
Expand All @@ -297,39 +325,37 @@ export function CommonMencloseMixin<
}

/**
* @return {number[]} Array of the maximum extra space from the notations along each side
* @return {Notation.PaddingData} Array of the maximum extra space from the notations along each side
*/
public getBBoxExtenders(): number[] {
let TRBL = [0, 0, 0, 0];
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 {number[]} Array of padding (i.e., BBox minus border) along each side
* @return {Notation.PaddingData} Array of padding (i.e., BBox minus border) along each side
*/
public getPadding(): number[] {
let TRBL = [0, 0, 0, 0];
let BTRBL = [0, 0, 0, 0];
public getPadding(): Notation.PaddingData {
let BTRBL = [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));
const border = this.notations[name].border;
if (border) {
this.maximizeEntries(BTRBL, border(this as any));
}
}
return [0, 1, 2, 3].map(i => TRBL[i] - BTRBL[i]);
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 {number[]} X An array of numbers
* @param {number[]} Y An array of numbers that replace smaller ones in X
* @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: number[], Y: number[]) {
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];
Expand All @@ -339,6 +365,18 @@ export function CommonMencloseMixin<

/********************************************************/

/**
* 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
Expand All @@ -354,9 +392,11 @@ export function CommonMencloseMixin<
* @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 = false): N {
public arrow(_w: number, _a: number, _double: boolean, _offset: string = '', _dist: number = 0): N {
return null as N;
}

Expand All @@ -378,6 +418,17 @@ export function CommonMencloseMixin<
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);
}

/********************************************************/

/**
Expand Down
Loading

0 comments on commit cd12327

Please sign in to comment.