import { Style } from '../public/core' ; import { Context } from './context' ; import { Box , BoxType } from './box' ; export type VBoxElement = { box : Box ; marginLeft ? : number ; marginRight ? : number ; classes ? : string [ ] ; style ? : Style ; } ; export type VBoxElementAndShift = VBoxElement & { shift : number ; } ; export type VBoxChild = VBoxElement | number ; type VBoxParam = { individualShift : VBoxElementAndShift [ ] ; } | { top : number ; children : VBoxChild [ ] ; } | { bottom : number ; children : VBoxChild [ ] ; } | { shift : number ; children : VBoxChild [ ] ; } | { firstBaseline : VBoxChild [ ] ; } ; function getVListChildrenAndDepth ( params ) { if ( 'individualShift' in params ) { const oldChildren = params . individualShift ; let prevChild = oldChildren [ 0 ] ; const children = [ prevChild ] ; const depth = - prevChild . shift - prevChild . box . depth ; let currPos = depth ; for ( let i = 1 ; i < oldChildren . length ; i ++ ) { const child = oldChildren [ i ] ; const diff = - child . shift - currPos - child . box . depth ; const size = diff - ( prevChild . box . height + prevChild . box . depth ) ; currPos = currPos + diff ; children . push ( size ) ; children . push ( child ) ; prevChild = child ; } return [ children , depth ] ; } if ( 'top' in params ) { let bottom = params . top ; for ( const child of params . children ) { bottom -= typeof child === 'number' ? child : child . box . height + child . box . depth ; } return [ params . children , bottom ] ; } else if ( 'bottom' in params ) { return [ params . children , - params . bottom ] ; } else if ( 'firstBaseline' in params ) { const firstChild = params . firstBaseline [ 0 ] ; if ( typeof firstChild === 'number' ) { throw new Error ( 'First child must be an element.' ) ; } return [ params . firstBaseline , - firstChild . box . depth ] ; } else if ( 'shift' in params ) { const firstChild = params . children [ 0 ] ; if ( typeof firstChild === 'number' ) { throw new Error ( 'First child must be an element.' ) ; } return [ params . children , - firstChild . box . depth - params . shift ] ; } return [ null , 0 ] ; } /**
 * Makes a vertical list by stacking elements and kerns on top of each other.
 * Allows for many different ways of specifying the positioning method.
 *
 * See VListParam documentation above.
 *
 * Return a single row if the stack is entirely above the baseline.
 * Otherwise return 2 rows, the second one representing depth below the baseline.
 * This is necessary to workaround a Safari... behavior (see vlist-s and vlist-t2)
 */ function makeRows ( params ) { const [ children , depth ] = getVListChildrenAndDepth ( params ) ; if ( ! children ) return [ [ ] , 0 , 0 ] ; let pstrutSize = 0 ; for ( const child of children ) { if ( typeof child !== 'number' ) { const box = child . box ; pstrutSize = Math . max ( pstrutSize , box . maxFontSize , box . height ) ; } } pstrutSize += 2 ; const pstrut = new Box ( null , { classes : 'pstrut' } ) ; pstrut . setStyle ( 'height' , pstrutSize , 'em' ) ; const realChildren = [ ] ; let minPos = depth ; let maxPos = depth ; let currPos = depth ; for ( const child of children ) { if ( typeof child === 'number' ) { currPos += child ; } else { const box = child . box ; const classes = child . classes ?? [ ] ; const childWrap = new Box ( [ pstrut , box ] , { classes : classes . join ( ' ' ) , style : child . style , } ) ; childWrap . setStyle ( 'top' , - pstrutSize - currPos - box . depth , 'em' ) ; if ( child . marginLeft ) { childWrap . setStyle ( 'margin-left' , child . marginLeft , 'em' ) ; } if ( child . marginRight ) { childWrap . setStyle ( 'margin-right' , child . marginRight , 'em' ) ; } realChildren . push ( childWrap ) ; currPos += box . height + box . depth ; } minPos = Math . min ( minPos , currPos ) ; maxPos = Math . max ( maxPos , currPos ) ; } const vlist = new Box ( realChildren , { classes : 'vlist' } ) ; vlist . setStyle ( 'height' , maxPos , 'em' ) ; let rows ; if ( minPos < 0 ) { const depthStrut = new Box ( new Box ( null ) , { classes : 'vlist' } ) ; depthStrut . setStyle ( 'height' , - minPos , 'em' ) ; const topStrut = new Box ( 0x200b , { classes : 'vlist-s' , maxFontSize : 0 , height : 0 , depth : 0 , } ) ; rows = [ new Box ( [ vlist , topStrut ] , { classes : 'vlist-r' } ) , new Box ( depthStrut , { classes : 'vlist-r' } ) , ] ; } else { rows = [ new Box ( vlist , { classes : 'vlist-r' } ) ] ; } return [ rows , maxPos , - minPos ] ; } export class VBox extends Box { constructor ( content , options ? ) { const [ rows , height , depth ] = makeRows ( content ) ; super ( rows . length === 1 ? rows [ 0 ] : rows , { classes : ( options ?. classes ?? '' ) + ' vlist-t' + ( rows . length === 2 ? ' vlist-t2' : '' ) , height , depth , type : options ?. type , } ) ; } } export function makeLimitsStack ( context , options ) { const metrics = context . metrics ; const base = new Box ( options . base ) ; const baseShift = options . baseShift ?? 0 ; const slant = options . slant ?? 0 ; let aboveShift = 0 ; let belowShift = 0 ; if ( options . above ) { aboveShift = options . aboveShift ?? Math . max ( metrics . bigOpSpacing1 , metrics . bigOpSpacing3 - options . above . depth ) ; } if ( options . below ) { belowShift = options . belowShift ?? Math . max ( metrics . bigOpSpacing2 , metrics . bigOpSpacing4 - options . below . height ) ; } let result = null ; if ( options . below && options . above ) { const bottom = metrics . bigOpSpacing5 + options . below . height + options . below . depth + belowShift + base . depth + baseShift ; result = new VBox ( { bottom , children : [ metrics . bigOpSpacing5 , { box : options . below , marginLeft : - slant , classes : [ 'ML__center' ] , } , belowShift , { box : base , classes : [ 'ML__center' ] } , aboveShift , { box : options . above , marginLeft : slant , classes : [ 'ML__center' ] , } , metrics . bigOpSpacing5 , ] , } ) . wrap ( context ) ; } else if ( options . below && ! options . above ) { result = new VBox ( { top : base . height - baseShift , children : [ metrics . bigOpSpacing5 , { box : options . below , marginLeft : - slant , classes : [ 'ML__center' ] , } , belowShift , { box : base , classes : [ 'ML__center' ] } , ] , } ) . wrap ( context ) ; } else if ( ! options . below && options . above ) { const bottom = base . depth + baseShift ; result = new VBox ( { bottom , children : [ { box : base , classes : [ 'ML__center' ] } , aboveShift , { box : options . above , marginLeft : slant , classes : [ 'ML__center' ] , } , metrics . bigOpSpacing5 , ] , } ) . wrap ( context ) ; } else { const bottom = base . depth + baseShift ; result = new VBox ( { bottom , children : [ { box : base } , metrics . bigOpSpacing5 ] , } ) . wrap ( context ) ; } console . assert ( options . type !== undefined ) ; return new Box ( result , { type : options . type ?? 'mop' } ) ; }