Skip to content

Commit 4c22ade

Browse files
committed
Merge branch 'mfrac-attributes' into alpha
2 parents b2cb495 + bd6d50d commit 4c22ade

File tree

3 files changed

+288
-41
lines changed

3 files changed

+288
-41
lines changed

mathjax3-ts/output/chtml/Wrapper.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import {AbstractWrapper} from '../../core/Tree/Wrapper.js';
2525
import {Node, PropertyList} from '../../core/Tree/Node.js';
26-
import {MmlNode, TextNode, AbstractMmlNode} from '../../core/MmlTree/MmlNode.js';
26+
import {MmlNode, TextNode, AbstractMmlNode, AttributeList} from '../../core/MmlTree/MmlNode.js';
2727
import {Property} from '../../core/Tree/Node.js';
2828
import {OptionList} from '../../util/Options.js';
2929
import {unicodeChars} from '../../util/string.js';
@@ -780,6 +780,29 @@ export class CHTMLWrapper<N, T, D> extends AbstractWrapper<MmlNode, CHTMLWrapper
780780
return (this.node as AbstractMmlNode).factory.create(kind, properties, children);
781781
}
782782

783+
/*
784+
* Create an mo wrapper with the given text,
785+
* link it in, and give it the right defaults.
786+
*
787+
* @param{string} text The text for the wrapped element
788+
* @return{CHTMLWrapper} The wrapped MmlMo node
789+
*/
790+
protected createMo(text: string): CHTMLmo<N, T, D> {
791+
const mmlFactory = (this.node as AbstractMmlNode).factory;
792+
const textNode = (mmlFactory.create('text') as TextNode).setText(text);
793+
const mml = mmlFactory.create('mo', {stretchy: true}, [textNode]);
794+
const attributes = this.node.attributes;
795+
const display = attributes.get('display') as boolean;
796+
const scriptlevel = attributes.get('scriptlevel') as number;
797+
const defaults: AttributeList = {
798+
mathsize: ['math', attributes.get('mathsize')]
799+
};
800+
mml.setInheritedAttributes(defaults, display, scriptlevel, false);
801+
const node = this.wrap(mml) as CHTMLmo<N, T, D>;
802+
node.parent = this;
803+
return node;
804+
}
805+
783806
}
784807

785808
/*

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

Lines changed: 261 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@
2222
*/
2323

2424
import {CHTMLWrapper} from '../Wrapper.js';
25+
import {CHTMLWrapperFactory} from '../WrapperFactory.js';
26+
import {CHTMLmo} from './mo.js';
2527
import {MmlMfrac} from '../../../core/MmlTree/MmlNodes/mfrac.js';
2628
import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
2729
import {BBox} from '../BBox.js';
2830
import {StyleList} from '../CssStyles.js';
31+
import {OptionList} from '../../../util/Options.js';
2932
import {DIRECTION} from '../FontData.js';
3033

3134
/*****************************************************************/
@@ -48,9 +51,15 @@ export class CHTMLmfrac<N, T, D> extends CHTMLWrapper<N, T, D> {
4851
'mjx-frac[type="d"]': {
4952
'vertical-align': '.04em' // axis_height - 3.5 * rule_thickness
5053
},
51-
'mjx-frac[delims="true"]': {
54+
'mjx-frac[delims]': {
5255
padding: '0 .1em' // .1 (for line's -.1em margin)
5356
},
57+
'mjx-frac[atop]': {
58+
padding: '0 .12em' // nulldelimiterspace
59+
},
60+
'mjx-frac[atop][delims]': {
61+
padding: '0'
62+
},
5463
'mjx-dtable': {
5564
display: 'inline-table',
5665
width: '100%'
@@ -75,10 +84,10 @@ export class CHTMLmfrac<N, T, D> extends CHTMLWrapper<N, T, D> {
7584
},
7685

7786
'mjx-den[align="right"], mjx-num[align="right"]': {
78-
align: 'right'
87+
'text-align': 'right'
7988
},
8089
'mjx-den[align="left"], mjx-num[align="left"]': {
81-
align: 'left'
90+
'text-align': 'left'
8291
},
8392

8493
'mjx-nstrut': {
@@ -115,22 +124,108 @@ export class CHTMLmfrac<N, T, D> extends CHTMLWrapper<N, T, D> {
115124

116125
};
117126

127+
protected bevel: CHTMLmo<N, T, D> = null;
128+
129+
/************************************************/
130+
131+
/*
132+
* @override
133+
* @constructor
134+
*/
135+
constructor(factory: CHTMLWrapperFactory<N, T, D>, node: MmlNode, parent: CHTMLWrapper<N, T, D> = null) {
136+
super(factory, node, parent);
137+
//
138+
// create internal bevel mo element
139+
//
140+
if (this.node.attributes.get('bevelled')) {
141+
const {H} = this.getBevelData(this.isDisplay());
142+
const bevel = this.bevel = this.createMo('/');
143+
bevel.canStretch(DIRECTION.Vertical);
144+
bevel.getStretchedVariant([H], true);
145+
}
146+
}
147+
118148
/*
119149
* @override
120150
*/
121151
public toCHTML(parent: N) {
122-
const chtml = this.standardCHTMLnode(parent);
123-
const attr = this.node.attributes.getList('displaystyle', 'scriptlevel');
124-
const style = (attr.displaystyle && attr.scriptlevel === 0 ? {type: 'd'} : {});
125-
const fstyle = (this.node.getProperty('withDelims') ? {...style, delims: 'true'} : style);
152+
this.standardCHTMLnode(parent);
153+
const {linethickness, bevelled} = this.node.attributes.getList('linethickness', 'bevelled');
154+
const display = this.isDisplay();
155+
if (bevelled) {
156+
this.makeBevelled(display);
157+
} else {
158+
const thickness = this.length2em(String(linethickness));
159+
if (thickness === 0) {
160+
this.makeAtop(display);
161+
} else {
162+
this.makeFraction(display, thickness);
163+
}
164+
}
165+
}
166+
167+
/*
168+
* @override
169+
*/
170+
public computeBBox(bbox: BBox) {
171+
bbox.empty();
172+
const {linethickness, bevelled} = this.node.attributes.getList('linethickness', 'bevelled');
173+
const display = this.isDisplay();
174+
if (bevelled) {
175+
this.getBevelledBBox(bbox, display);
176+
} else {
177+
const thickness = this.length2em(String(linethickness));
178+
if (thickness === 0) {
179+
this.getAtopBBox(bbox, display);
180+
} else {
181+
this.getFractionBBox(bbox, display, thickness);
182+
}
183+
}
184+
bbox.clean();
185+
}
186+
187+
/************************************************/
188+
189+
/*
190+
* @param{boolean} display True when fraction is in display mode
191+
* @param{number} t The rule line thickness
192+
*/
193+
protected makeFraction(display: boolean, t: number) {
194+
const {numalign, denomalign} = this.node.attributes.getList('numalign', 'denomalign');
195+
const withDelims = this.node.getProperty('withDelims');
196+
//
197+
// Attributes to set for the different elements making up the fraction
198+
//
199+
const attr = (display ? {type: 'd'} : {}) as OptionList;
200+
const fattr = (withDelims ? {...attr, delims: 'true'} : {...attr}) as OptionList;
201+
const nattr = (numalign !== 'center' ? {align: numalign} : {}) as OptionList;
202+
const dattr = (denomalign !== 'center' ? {align: denomalign} : {}) as OptionList;
203+
const dsattr = {...attr}, nsattr = {...attr};
204+
//
205+
// Set the styles to handle the linethickness, if needed
206+
//
207+
const fparam = this.font.params;
208+
if (t !== .06) {
209+
const a = fparam.axis_height;
210+
const tEm = this.em(t), T = (display ? 3.5 : 1.5) * t;
211+
const m = (display ? this.em(3 * t) : tEm) + ' -.1em';
212+
attr.style = {height: tEm, 'border-top': tEm + ' solid', margin: m};
213+
const nh = this.em(Math.max(0, (display ? fparam.num1 : fparam.num2) - a - T));
214+
nsattr.style = {height: nh, 'vertical-align': '-' + nh};
215+
dsattr.style = {height: this.em(Math.max(0, (display ? fparam.denom1 : fparam.denom2) + a - T))};
216+
fattr.style = {'vertical-align': this.em(a - T)};
217+
}
218+
//
219+
// Create the DOM tree
220+
//
126221
let num, den;
127-
this.adaptor.append(chtml, this.html('mjx-frac', fstyle, [
128-
num = this.html('mjx-num', {}, [this.html('mjx-nstrut', style)]),
222+
this.adaptor.append(this.chtml, this.html('mjx-frac', fattr, [
223+
num = this.html('mjx-num', nattr, [this.html('mjx-nstrut', nsattr)]),
129224
this.html('mjx-dbox', {}, [
130225
this.html('mjx-dtable', {}, [
131-
this.html('mjx-line', style),
226+
this.html('mjx-line', attr),
132227
this.html('mjx-row', {}, [
133-
den = this.html('mjx-den', {}, [this.html('mjx-dstrut', style)])
228+
den = this.html('mjx-den', dattr, [this.html('mjx-dstrut', dsattr)])
134229
])
135230
])
136231
])
@@ -140,27 +235,171 @@ export class CHTMLmfrac<N, T, D> extends CHTMLWrapper<N, T, D> {
140235
}
141236

142237
/*
143-
* @override
238+
* @param{BBox} bbox The buonding box to modify
239+
* @param{boolean} display True for display-mode fractions
240+
* @param{number} t The thickness of the line
144241
*/
145-
public computeBBox(bbox: BBox) {
146-
bbox.empty();
147-
const attr = this.node.attributes.getList('displaystyle', 'scriptlevel');
148-
const display = attr.displaystyle && attr.scriptlevel === 0;
242+
protected getFractionBBox(bbox: BBox, display: boolean, t: number) {
149243
const nbox = this.childNodes[0].getBBox();
150244
const dbox = this.childNodes[1].getBBox();
151-
const pad = ((this.node.getProperty('withDelims') as boolean) ? 0 : this.font.params.nulldelimiterspace);
152-
const a = this.font.params.axis_height;
153-
const T = (display ? 3.5 : 1.5) * this.font.params.rule_thickness;
154-
bbox.combine(nbox, 0, a + T + Math.max(nbox.d * nbox.rscale, display ? .217 : .054));
155-
bbox.combine(dbox, 0, a - T - Math.max(dbox.h * dbox.rscale, display ? .726 : .505));
245+
const fparam = this.font.params;
246+
const pad = (this.node.getProperty('withDelims') as boolean ? 0 : fparam.nulldelimiterspace);
247+
const a = fparam.axis_height;
248+
const T = (display ? 3.5 : 1.5) * t;
249+
bbox.combine(nbox, 0, a + T + Math.max(nbox.d * nbox.rscale, (display ? fparam.num1 : fparam.num2) - a - T));
250+
bbox.combine(dbox, 0, a - T - Math.max(dbox.h * dbox.rscale, (display ? fparam.denom1 : fparam.denom2) + a - T));
156251
bbox.w += 2 * pad + .2;
157-
bbox.clean();
158252
}
159253

254+
/************************************************/
255+
256+
/*
257+
* @param{boolean} display True when fraction is in display mode
258+
*/
259+
protected makeAtop(display: boolean) {
260+
const {numalign, denomalign} = this.node.attributes.getList('numalign', 'denomalign');
261+
const withDelims = this.node.getProperty('withDelims');
262+
//
263+
// Attributes to set for the different elements making up the fraction
264+
//
265+
const attr = (display ? {type: 'd', atop: true} : {atop: true}) as OptionList;
266+
const fattr = (withDelims ? {...attr, delims: true} : {...attr}) as OptionList;
267+
const nattr = (numalign !== 'center' ? {align: numalign} : {}) as OptionList;
268+
const dattr = (denomalign !== 'center' ? {align: denomalign} : {}) as OptionList;
269+
//
270+
// Determine sparation and positioning
271+
//
272+
const {v, q} = this.getUVQ(display);
273+
nattr.style = {'padding-bottom': this.em(q)};
274+
fattr.style = {'vertical-align': this.em(-v)};
275+
//
276+
// Create the DOM tree
277+
//
278+
let num, den;
279+
this.adaptor.append(this.chtml, this.html('mjx-frac', fattr, [
280+
num = this.html('mjx-num', nattr),
281+
den = this.html('mjx-den', dattr)
282+
]));
283+
this.childNodes[0].toCHTML(num);
284+
this.childNodes[1].toCHTML(den);
285+
}
286+
287+
/*
288+
* @param{BBox} bbox The bounding box to modify
289+
* @param{boolean} display True for display-mode fractions
290+
*/
291+
protected getAtopBBox(bbox: BBox, display: boolean) {
292+
const fparam = this.font.params;
293+
const pad = (this.node.getProperty('withDelims') as boolean ? 0 : fparam.nulldelimiterspace);
294+
const {u, v, nbox, dbox} = this.getUVQ(display);
295+
bbox.combine(nbox, 0, u);
296+
bbox.combine(dbox, 0, -v);
297+
bbox.w += 2 * pad;
298+
}
299+
300+
/*
301+
* @param{boolean} display True for diplay-mode fractions
302+
* @return{Object}
303+
* The vertical offsets of the numerator (u), the denominator (v),
304+
* the separation between the two, and the bboxes themselves.
305+
*/
306+
protected getUVQ(display: boolean) {
307+
const nbox = this.childNodes[0].getBBox();
308+
const dbox = this.childNodes[1].getBBox();
309+
const fparam = this.font.params;
310+
//
311+
// Initial offsets (u, v)
312+
// Minimum separation (p)
313+
// Actual separation with initial positions (q)
314+
//
315+
let [u, v] = (display ? [fparam.num1, fparam.denom1] : [fparam.num3, fparam.denom2]);
316+
let p = (display ? 7 : 3) * fparam.rule_thickness;
317+
let q = (u - nbox.d * nbox.scale) - (dbox.h * dbox.scale - v);
318+
//
319+
// If actual separation is less than minimum, move them farther apart
320+
//
321+
if (q < p) {
322+
u += (p - q)/2;
323+
v += (p - q)/2;
324+
q = p;
325+
}
326+
return {u, v, q, nbox, dbox};
327+
}
328+
329+
/************************************************/
330+
331+
/*
332+
* @param{boolean} display True when fraction is in display mode
333+
*/
334+
protected makeBevelled(display: boolean) {
335+
const adaptor = this.adaptor;
336+
//
337+
// Create HTML tree
338+
//
339+
this.childNodes[0].toCHTML(this.chtml);
340+
this.bevel.toCHTML(this.chtml);
341+
this.childNodes[1].toCHTML(this.chtml);
342+
//
343+
// Place the parts
344+
//
345+
const {u, v, delta, nbox, dbox} = this.getBevelData(display);
346+
if (u) {
347+
const num = this.childNodes[0].chtml;
348+
adaptor.setStyle(num, 'verticalAlign', this.em(u / nbox.scale));
349+
}
350+
if (v) {
351+
const denom = this.childNodes[1].chtml;
352+
adaptor.setStyle(denom, 'verticalAlign', this.em(v / dbox.scale));
353+
}
354+
const dx = this.em(-delta / 2);
355+
adaptor.setStyle(this.bevel.chtml, 'marginLeft', dx);
356+
adaptor.setStyle(this.bevel.chtml, 'marginRight', dx);
357+
}
358+
359+
/*
360+
* @param{BBox} bbox The boundng box to modify
361+
* @param{boolean} display True for display-mode fractions
362+
*/
363+
protected getBevelledBBox(bbox: BBox, display: boolean) {
364+
const {u, v, delta, nbox, dbox} = this.getBevelData(display);
365+
const lbox = this.bevel.getBBox();
366+
bbox.combine(nbox, 0, u);
367+
bbox.combine(lbox, bbox.w - delta / 2, 0);
368+
bbox.combine(dbox, bbox.w - delta / 2, v);
369+
}
370+
371+
/*
372+
* @param{boolean} display True for display-style fractions
373+
* @return{Object} The height (H) of the bevel, horizontal offest (delta)
374+
* vertical offsets (u and v) of the parts, and
375+
* bounding boxes of the parts.
376+
*/
377+
protected getBevelData(display: boolean) {
378+
const nbox = this.childNodes[0].getBBox();
379+
const dbox = this.childNodes[1].getBBox();
380+
const delta = (display ? .4 : .15);
381+
const H = Math.max(nbox.scale * (nbox.h + nbox.d), dbox.scale * (dbox.h + dbox.d)) + 2 * delta;
382+
const a = this.font.params.axis_height;
383+
const u = nbox.scale * (nbox.d - nbox.h) / 2 + a + delta;
384+
const v = dbox.scale * (dbox.d - dbox.h) / 2 + a - delta;
385+
return {H, delta, u, v, nbox, dbox};
386+
}
387+
388+
389+
/************************************************/
390+
160391
/*
161392
* @override
162393
*/
163394
public canStretch(direction: DIRECTION) {
164395
return false;
165396
}
397+
398+
/*
399+
* @return{boolean} True if in display mode, false otherwise
400+
*/
401+
protected isDisplay() {
402+
const {displaystyle, scriptlevel} = this.node.attributes.getList('displaystyle', 'scriptlevel');
403+
return displaystyle && scriptlevel === 0;
404+
}
166405
}

0 commit comments

Comments
 (0)