import type { Style } from '../public/core' ; import { Atom , ToLatexOptions } from '../core/atom-class' ; import type { MathstyleName } from '../core/mathstyle' ; import { Box } from '../core/box' ; import { VBox } from '../core/v-box' ; import { makeCustomSizedDelim , makeNullDelimiter } from '../core/delimiters' ; import { Context } from '../core/context' ; import { AXIS_HEIGHT } from '../core/font-metrics' ; export type GenfracOptions = { continuousFraction ? : boolean ; numerPrefix ? : string ; denomPrefix ? : string ; leftDelim ? : string ; rightDelim ? : string ; hasBarLine ? : boolean ; mathstyleName ? : MathstyleName ; style ? : Style ; serialize ? : ( atom : GenfracAtom , options : ToLatexOptions ) => string ; } ; /**
 * Genfrac -- Generalized Fraction
 *
 * Decompose fractions, binomials, and in general anything made
 * of a numerator and denominator, optionally separated by a fraction bar,
 * and optionally surrounded by delimiters (parentheses, brackets, etc...).
 *
 * Depending on the type of fraction the mathstyle is either
 * displaystyle or textstyle. This value can also be set to 'auto',
 * to indicate it should use the current mathstyle
 */ export class GenfracAtom extends Atom { hasBarLine : boolean ; leftDelim ? : string ; rightDelim ? : string ; private readonly continuousFraction : boolean ; private readonly numerPrefix ? : string ; private readonly denomPrefix ? : string ; private readonly mathstyleName ? : MathstyleName ; constructor ( command , above , below , options ) { super ( 'genfrac' , { style : options . style , command , serialize : options . serialize , displayContainsHighlight : true , } ) ; this . above = above ; this . below = below ; this . hasBarLine = options ?. hasBarLine ?? true ; this . continuousFraction = options ?. continuousFraction ?? false ; this . numerPrefix = options ?. numerPrefix ; this . denomPrefix = options ?. denomPrefix ; this . mathstyleName = options ?. mathstyleName ; this . leftDelim = options ?. leftDelim ; this . rightDelim = options ?. rightDelim ; } serialize ( options ) { return ( this . command + `{${ this . aboveToLatex ( options ) }}` + `{${ this . belowToLatex ( options ) }}` ) ; } render ( context ) { const fracContext = new Context ( context , this . style , this . mathstyleName ) ; const metrics = fracContext . metrics ; const numContext = new Context ( fracContext , this . style , this . continuousFraction ? '' : 'numerator' ) ; const numerBox = this . numerPrefix ? new Box ( [ new Box ( this . numerPrefix ) , Atom . createBox ( numContext , this . above ) ] , { isTight : numContext . isTight , newList : true } ) : Atom . createBox ( numContext , this . above , { newList : true } ) ?? new Box ( null , { newList : true } ) ; const denomContext = new Context ( fracContext , this . style , this . continuousFraction ? '' : 'denominator' ) ; const denomBox = this . denomPrefix ? new Box ( [ new Box ( this . denomPrefix ) , Atom . createBox ( denomContext , this . below , { newList : true } ) , ] ) : Atom . createBox ( denomContext , this . below , { newList : true } ) ?? new Box ( null , { newList : true } ) ; const ruleWidth = this . hasBarLine ? metrics . defaultRuleThickness : 0 ; let numerShift ; let clearance = 0 ; let denomShift ; if ( fracContext . isDisplayStyle ) { numerShift = metrics . num1 ; clearance = ruleWidth > 0 ? 3 * ruleWidth : 7 * ruleWidth ; denomShift = metrics . denom1 ; } else { if ( ruleWidth > 0 ) { numerShift = metrics . num2 ; clearance = ruleWidth ; } else { numerShift = metrics . num3 ; clearance = 3 * ruleWidth ; } denomShift = metrics . denom2 ; } const numerDepth = numerBox . depth ; const denomHeight = denomBox . height ; let frac ; if ( ruleWidth <= 0 ) { const candidateClearance = numerShift - numerDepth - ( denomHeight - denomShift ) ; if ( candidateClearance < clearance ) { numerShift += ( clearance - candidateClearance ) / 2 ; denomShift += ( clearance - candidateClearance ) / 2 ; } frac = new VBox ( { individualShift : [ { box : numerBox , shift : - numerShift , classes : [ 'ML__center' ] , } , { box : denomBox , shift : denomShift , classes : [ 'ML__center' ] , } , ] , } ) . wrap ( fracContext ) ; } else { const numerLine = AXIS_HEIGHT + ruleWidth / 2 ; const denomLine = AXIS_HEIGHT - ruleWidth / 2 ; if ( numerShift < clearance + numerDepth + numerLine ) { numerShift = clearance + numerDepth + numerLine ; } if ( denomShift < clearance + denomHeight - denomLine ) { denomShift = clearance + denomHeight - denomLine ; } const fracLine = new Box ( null , { classes : 'ML__frac-line' , mode : this . mode , style : this . style , } ) ; fracLine . height = ruleWidth / 2 ; fracLine . depth = ruleWidth / 2 ; frac = new VBox ( { individualShift : [ { box : denomBox , shift : denomShift , classes : [ 'ML__center' ] , } , { box : fracLine , shift : - denomLine } , { box : numerBox , shift : - numerShift , classes : [ 'ML__center' ] , } , ] , } ) . wrap ( fracContext ) ; } const delimSize = fracContext . isDisplayStyle ? metrics . delim1 : metrics . delim2 ; const leftDelim = this . leftDelim ? this . bind ( context , makeCustomSizedDelim ( 'mopen' , this . leftDelim , delimSize , true , context , { style : this . style , mode : this . mode } ) ) : makeNullDelimiter ( fracContext , 'mopen' ) ; let rightDelim = null ; if ( this . continuousFraction ) { rightDelim = new Box ( null , { type : 'mclose' } ) ; } else if ( ! this . rightDelim ) { rightDelim = makeNullDelimiter ( fracContext , 'mclose' ) ; } else { rightDelim = this . bind ( context , makeCustomSizedDelim ( 'mclose' , this . rightDelim , delimSize , true , context , { style : this . style , mode : this . mode } ) ) ; } const result = this . bind ( context , new Box ( [ leftDelim , frac , rightDelim ] , { isTight : fracContext . isTight , type : 'mord' , classes : 'mfrac' , } ) ) ; if ( ! result ) return null ; if ( this . caret ) result . caret = this . caret ; return this . attachSupsub ( context , { base : result } ) ; } }