2222 */
2323
2424import { CHTMLWrapper } from '../Wrapper.js' ;
25+ import { CHTMLWrapperFactory } from '../WrapperFactory.js' ;
26+ import { CHTMLmo } from './mo.js' ;
2527import { MmlMfrac } from '../../../core/MmlTree/MmlNodes/mfrac.js' ;
2628import { MmlNode } from '../../../core/MmlTree/MmlNode.js' ;
2729import { BBox } from '../BBox.js' ;
2830import { StyleList } from '../CssStyles.js' ;
31+ import { OptionList } from '../../../util/Options.js' ;
2932import { 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