Skip to content

Commit 3b7fd80

Browse files
committed
Add support for horizontal stretchy characters in munderover (still need to do mtd)
1 parent d8e3912 commit 3b7fd80

File tree

10 files changed

+198
-56
lines changed

10 files changed

+198
-56
lines changed

mathjax3-ts/output/chtml/FontData.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ export type DelimiterMap = {
107107
[n: number]: DelimiterData;
108108
};
109109

110+
export const NOSTRETCH: DelimiterData = {dir: DIRECTION.None};
111+
110112
/*
111113
* Font parameters (for TeX typesetting rules)
112114
*/

mathjax3-ts/output/chtml/Wrapper.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {CHTML} from '../chtml.js';
3333
import {CHTMLWrapperFactory} from './WrapperFactory.js';
3434
import {CHTMLmo} from './Wrappers/mo.js';
3535
import {BBox, BBoxData} from './BBox.js';
36-
import {FontData, DIRECTION} from './FontData.js';
36+
import {FontData, DelimiterData, DIRECTION, NOSTRETCH} from './FontData.js';
3737
import {StyleList} from './CssStyles.js';
3838

3939
/*****************************************************************/
@@ -226,9 +226,9 @@ export class CHTMLWrapper extends AbstractWrapper<MmlNode, CHTMLWrapper> {
226226
protected bboxComputed: boolean = false;
227227

228228
/*
229-
* Direction this node can be stretched (null means not yet determined)
229+
* Delimiter data for stretching this node (NOSTRETCH means not yet determined)
230230
*/
231-
public stretch: DIRECTION = DIRECTION.None;
231+
public stretch: DelimiterData = NOSTRETCH;
232232

233233
/*
234234
* Easy access to the font parameters
@@ -620,16 +620,16 @@ export class CHTMLWrapper extends AbstractWrapper<MmlNode, CHTMLWrapper> {
620620
* @return{boolean} Whether the node can stretch in that direction
621621
*/
622622
public canStretch(direction: DIRECTION): boolean {
623-
this.stretch = DIRECTION.None;
623+
this.stretch = NOSTRETCH;
624624
if (this.node.isEmbellished) {
625625
let core = this.core();
626626
if (core && core.node !== this.node) {
627627
if (core.canStretch(direction)) {
628-
this.stretch = direction;
628+
this.stretch = core.stretch;
629629
}
630630
}
631631
}
632-
return this.stretch !== DIRECTION.None;
632+
return this.stretch.dir !== DIRECTION.None;
633633
}
634634

635635
/*******************************************************************/

mathjax3-ts/output/chtml/Wrappers/TextNode.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export class CHTMLTextNode extends CHTMLWrapper {
4848
let text = (this.node as TextNode).getText();
4949
if (this.parent.variant === '-explicitFont') {
5050
parent.appendChild(this.text(text));
51+
} else if (this.parent.stretch.c) {
52+
parent.appendChild(this.html('mjx-c', {c: this.char(this.parent.stretch.c)}));
5153
} else {
5254
for (const n of this.unicodeChars(text)) {
5355
parent.appendChild(this.html('mjx-c', {c: this.char(n)}));
@@ -63,7 +65,8 @@ export class CHTMLTextNode extends CHTMLWrapper {
6365
if (variant === '-explicitFont') {
6466
// FIXME: measure this using DOM, if possible
6567
} else {
66-
const chars = this.unicodeChars((this.node as TextNode).getText());
68+
const c = this.parent.stretch.c;
69+
const chars = (c ? [c] : this.unicodeChars((this.node as TextNode).getText()));
6770
let [h, d, w, data] = this.getChar(variant, chars[0]);
6871
bbox.h = h;
6972
bbox.d = d;

mathjax3-ts/output/chtml/Wrappers/mo.ts

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
2828
import {BBox} from '../BBox.js';
2929
import {DelimiterData} from '../FontData.js';
3030
import {StyleList} from '../CssStyles.js';
31-
import {DIRECTION} from '../FontData.js';
31+
import {DIRECTION, NOSTRETCH} from '../FontData.js';
3232

3333
/*
3434
* Convert direction to letter
@@ -68,8 +68,13 @@ export class CHTMLmo extends CHTMLWrapper {
6868
width: '100%'
6969
},
7070
'mjx-stretchy-h > mjx-ext > mjx-c': {
71-
transform: 'scalex(1000)',
72-
margin: '0 -1em'
71+
transform: 'scalex(1000)'
72+
},
73+
'mjx-stretchy-h > mjx-beg > mjx-c': {
74+
'margin-right': '-.1em'
75+
},
76+
'mjx-stretchy-h > mjx-end > mjx-c': {
77+
'margin-left': '-.1em'
7378
},
7479

7580
'mjx-stretchy-v': {
@@ -123,15 +128,16 @@ export class CHTMLmo extends CHTMLWrapper {
123128
public toCHTML(parent: HTMLElement) {
124129
// eventually handle centering, largop, etc.
125130
const attributes = this.node.attributes;
126-
const symmetric = attributes.get('symmetric') as boolean;
127-
if (this.stretch && this.size === null) {
131+
const symmetric = (attributes.get('symmetric') as boolean) && this.stretch.dir !== DIRECTION.Horizontal;
132+
const stretchy = this.stretch.dir !== DIRECTION.None;
133+
if (stretchy && this.size === null) {
128134
this.getStretchedVariant([]);
129135
}
130136
let chtml = this.standardCHTMLnode(parent);
131137
if (this.noIC) {
132138
chtml.setAttribute('noIC', 'true');
133139
}
134-
if (this.stretch && this.size < 0) {
140+
if (stretchy && this.size < 0) {
135141
this.stretchHTML(chtml, symmetric);
136142
} else {
137143
if (symmetric || attributes.get('largeop')) {
@@ -151,16 +157,17 @@ export class CHTMLmo extends CHTMLWrapper {
151157
*/
152158
protected stretchHTML(chtml: HTMLElement, symmetric: boolean) {
153159
const c = this.getText().charCodeAt(0);
154-
const C = this.font.getDelimiter(c).stretch;
160+
const delim = this.stretch;
161+
const stretch = delim.stretch;
155162
const content: HTMLElement[] = [];
156163
//
157164
// Set up the beginning, extension, and end pieces
158165
//
159-
if (C[0]) {
166+
if (stretch[0]) {
160167
content.push(this.html('mjx-beg', {}, [this.html('mjx-c')]));
161168
}
162169
content.push(this.html('mjx-ext', {}, [this.html('mjx-c')]));
163-
if (C.length === 4) {
170+
if (stretch.length === 4) {
164171
//
165172
// Braces have a middle and second extensible piece
166173
//
@@ -169,15 +176,15 @@ export class CHTMLmo extends CHTMLWrapper {
169176
this.html('mjx-ext', {}, [this.html('mjx-c')])
170177
);
171178
}
172-
if (C[2]) {
179+
if (stretch[2]) {
173180
content.push(this.html('mjx-end', {}, [this.html('mjx-c')]));
174181
}
175182
//
176183
// Set the styles needed
177184
//
178185
const styles: StringMap = {};
179186
const {h, d, w} = this.bbox;
180-
if (this.stretch === DIRECTION.Vertical) {
187+
if (delim.dir === DIRECTION.Vertical) {
181188
//
182189
// Vertical needs an extra (empty) element to get vertical position right
183190
// in some browsers (e.g., Safari)
@@ -191,27 +198,28 @@ export class CHTMLmo extends CHTMLWrapper {
191198
//
192199
// Make the main element and add it to the parent
193200
//
194-
const dir = DirectionVH[this.stretch];
195-
const html = this.html('mjx-stretchy-' + dir, {c: this.char(c), style: styles}, content);
201+
const dir = DirectionVH[delim.dir];
202+
const properties = {c: this.char(delim.c || c), style: styles};
203+
const html = this.html('mjx-stretchy-' + dir, properties, content);
196204
chtml.appendChild(html);
197205
}
198206

199207
/*
200208
* @override
201209
*/
202210
public computeBBox(bbox: BBox) {
203-
const symmetric = this.node.attributes.get('symmetric');
204-
if (this.stretch && this.size === null) {
211+
const stretchy = (this.stretch.dir !== DIRECTION.None);
212+
if (stretchy && this.size === null) {
205213
this.getStretchedVariant([0]);
206214
}
207-
if (this.stretch && this.size < 0) return;
215+
if (stretchy && this.size < 0) return;
208216
super.computeBBox(bbox);
209217
const child = this.childNodes[this.childNodes.length-1];
210218
if (child && child.bbox.ic) {
211219
bbox.ic = child.bbox.ic;
212220
if (!this.noIC) bbox.w += bbox.ic;
213221
}
214-
if (symmetric) {
222+
if (this.node.attributes.get('symmetric') && this.stretch.dir !== DIRECTION.Horizontal) {
215223
const d = ((bbox.h + bbox.d) / 2 + this.font.params.axis_height) - bbox.h;
216224
bbox.h += d;
217225
bbox.d += d;
@@ -238,8 +246,8 @@ export class CHTMLmo extends CHTMLWrapper {
238246
const c = this.getText();
239247
if (c.length !== 1) return false;
240248
const delim = this.font.getDelimiter(c.charCodeAt(0));
241-
this.stretch = (delim && delim.dir === direction ? delim.dir : DIRECTION.None);
242-
return this.stretch !== DIRECTION.None;
249+
this.stretch = (delim && delim.dir === direction ? delim : NOSTRETCH);
250+
return this.stretch.dir !== DIRECTION.None;
243251
}
244252

245253
/*
@@ -249,7 +257,7 @@ export class CHTMLmo extends CHTMLWrapper {
249257
* @param{boolean} exact True if not allowed to use delimiter factor and shortfall
250258
*/
251259
public getStretchedVariant(WH: number[], exact: boolean = false) {
252-
if (this.stretch) {
260+
if (this.stretch.dir !== DIRECTION.None) {
253261
let D = this.getWH(WH);
254262
const min = this.getSize('minsize', 0);
255263
const max = this.getSize('maxsize', Infinity);
@@ -263,16 +271,18 @@ export class CHTMLmo extends CHTMLWrapper {
263271
//
264272
// Look through the delimiter sizes for one that matches
265273
//
266-
const c = this.getText().charCodeAt(0);
267-
const delim = this.font.getDelimiter(c);
274+
const delim = this.stretch;
275+
const c = delim.c || this.getText().charCodeAt(0);
268276
let i = 0;
269-
for (const d of delim.sizes) {
270-
if (d >= m) {
271-
this.variant = this.font.getSizeVariant(c, i);
272-
this.size = i;
273-
return;
277+
if (delim.sizes) {
278+
for (const d of delim.sizes) {
279+
if (d >= m) {
280+
this.variant = this.font.getSizeVariant(c, i);
281+
this.size = i;
282+
return;
283+
}
284+
i++;
274285
}
275-
i++;
276286
}
277287
//
278288
// No size matches, so if we can make multi-character delimiters,
@@ -289,8 +299,9 @@ export class CHTMLmo extends CHTMLWrapper {
289299
}
290300

291301
/*
292-
* @param{string} name The name of the attribute to fix
302+
* @param{string} name The name of the attribute to get
293303
* @param{number} value The default value to use
304+
* @return{number} The size in em's of the attribute (or the default value)
294305
*/
295306
protected getSize(name: string, value: number) {
296307
let attributes = this.node.attributes;
@@ -302,6 +313,7 @@ export class CHTMLmo extends CHTMLWrapper {
302313

303314
/*
304315
* @param{number[]} WH Either [W] for width, [H, D] for height and depth, or [] for min/max size
316+
* @return{number} Either the width or the total height of the character
305317
*/
306318
protected getWH(WH: number[]) {
307319
if (WH.length === 0) return 0;
@@ -318,7 +330,7 @@ export class CHTMLmo extends CHTMLWrapper {
318330
*/
319331
protected getStretchBBox(WHD: number[], D: number, C: DelimiterData) {
320332
let [h, d, w] = C.HDW;
321-
if (this.stretch === DIRECTION.Vertical) {
333+
if (this.stretch.dir === DIRECTION.Vertical) {
322334
[h, d] = this.getBaseline(WHD, D, C);
323335
} else {
324336
w = D;
@@ -332,7 +344,7 @@ export class CHTMLmo extends CHTMLWrapper {
332344
* @param{number[]} WHD The [H, D] being requested from the parent mrow
333345
* @param{number} HD The full height (including symmetry, etc)
334346
* @param{DelimiterData} C The delimiter data for the stretchy character
335-
* @param{number[]} The height and depth for the vertically stretched delimiter
347+
* @return{number[]} The height and depth for the vertically stretched delimiter
336348
*/
337349
protected getBaseline(WHD: number[], HD: number, C: DelimiterData) {
338350
const hasWHD = (WHD.length === 2);

mathjax3-ts/output/chtml/Wrappers/mrow.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export class CHTMLmrow extends CHTMLWrapper {
105105
//
106106
let all = (count > 1 && count === nodeCount);
107107
for (const child of this.childNodes) {
108-
const noStretch = !child.stretch;
108+
const noStretch = (child.stretch.dir === DIRECTION.None);
109109
if (all || noStretch) {
110110
const {h, d} = child.getBBox(noStretch);
111111
if (h > H) H = h;

mathjax3-ts/output/chtml/Wrappers/munderover.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424

2525
import {CHTMLWrapper} from '../Wrapper.js';
26+
import {CHTMLWrapperFactory} from '../WrapperFactory.js';
2627
import {CHTMLmsubsup, CHTMLmsub, CHTMLmsup} from './msubsup.js';
2728
import {MmlMunderover, MmlMunder, MmlMover} from '../../../core/MmlTree/MmlNodes/munderover.js';
2829
import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
@@ -63,6 +64,15 @@ export class CHTMLmunder extends CHTMLmsub {
6364
return this.childNodes[(this.node as MmlMunder).under];
6465
}
6566

67+
/*
68+
* @override
69+
* @constructor
70+
*/
71+
constructor(factory: CHTMLWrapperFactory, node: MmlNode, parent: CHTMLWrapper = null) {
72+
super(factory, node, parent);
73+
this.stretchChildren();
74+
}
75+
6676
/*
6777
* @override
6878
*/
@@ -134,6 +144,15 @@ export class CHTMLmover extends CHTMLmsup {
134144
return this.childNodes[(this.node as MmlMover).over];
135145
}
136146

147+
/*
148+
* @override
149+
* @constructor
150+
*/
151+
constructor(factory: CHTMLWrapperFactory, node: MmlNode, parent: CHTMLWrapper = null) {
152+
super(factory, node, parent);
153+
this.stretchChildren();
154+
}
155+
137156
/*
138157
* @override
139158
*/
@@ -232,6 +251,15 @@ export class CHTMLmunderover extends CHTMLmsubsup {
232251
return this.over;
233252
}
234253

254+
/*
255+
* @override
256+
* @constructor
257+
*/
258+
constructor(factory: CHTMLWrapperFactory, node: MmlNode, parent: CHTMLWrapper = null) {
259+
super(factory, node, parent);
260+
this.stretchChildren();
261+
}
262+
235263
/*
236264
* @override
237265
*/

mathjax3-ts/output/chtml/Wrappers/scriptbase.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {MmlMsubsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
3131
import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
3232
import {BBox} from '../BBox.js';
3333
import {StyleData, StyleList} from '../CssStyles.js';
34+
import {DIRECTION} from '../FontData.js';
3435

3536
/*****************************************************************/
3637
/*
@@ -262,4 +263,43 @@ export class CHTMLscriptbase extends CHTMLWrapper {
262263
}
263264
}
264265
}
266+
267+
/*
268+
* Handle horizontal stretching of children to match greatest width
269+
* of all children
270+
*/
271+
protected stretchChildren() {
272+
let stretchy: CHTMLWrapper[] = [];
273+
//
274+
// Locate and count the stretchy children
275+
//
276+
for (const child of this.childNodes) {
277+
if (child.canStretch(DIRECTION.Horizontal)) {
278+
stretchy.push(child);
279+
}
280+
}
281+
let count = stretchy.length;
282+
let nodeCount = this.childNodes.length;
283+
if (count && nodeCount > 1) {
284+
let W = 0;
285+
//
286+
// If all the children are stretchy, find the largest one,
287+
// otherwise, find the width of the non-stretchy children.
288+
//
289+
let all = (count > 1 && count === nodeCount);
290+
for (const child of this.childNodes) {
291+
const noStretch = (child.stretch.dir === DIRECTION.None);
292+
if (all || noStretch) {
293+
const {w} = child.getBBox(noStretch);
294+
if (w > W) W = w;
295+
}
296+
}
297+
//
298+
// Stretch the stretchable children
299+
//
300+
for (const child of stretchy) {
301+
child.coreMO().getStretchedVariant([W / child.bbox.rscale]);
302+
}
303+
}
304+
}
265305
}

0 commit comments

Comments
 (0)