summary import { BoxType , Box } from './box' ; import { VBoxChild , VBox } from './v-box' ; import { MathstyleName } from './mathstyle' ; import { getCharacterMetrics , PT_PER_EM , AXIS_HEIGHT , FONT_SCALE , } from './font-metrics' ; import { Context } from './context' ; import { ParseMode , Style } from '../public/core' ; export const RIGHT_DELIM = { '(' : ')' , '{' : '}' , '[' : ']' , '|' : '|' , '\\lbrace' : '\\rbrace' , '\\lparen' : '\\rparen' , '\\{' : '\\}' , '\\langle' : '\\rangle' , '\\lfloor' : '\\rfloor' , '\\lceil' : '\\rceil' , '\\vert' : '\\vert' , '\\lvert' : '\\rvert' , '\\Vert' : '\\Vert' , '\\lVert' : '\\rVert' , '\\lbrack' : '\\rbrack' , '\\ulcorner' : '\\urcorner' , '\\llcorner' : '\\lrcorner' , '\\lgroup' : '\\rgroup' , '\\lmoustache' : '\\rmoustache' , } ; function getSymbolValue ( symbol ) { return ( { '[' : 0x5b , ']' : 0x5d , '(' : 0x28 , ')' : 0x29 , '\\mid' : 0x2223 , '|' : 0x2223 , '\u2223' : 0x2223 , '\u2225' : 0x2225 , '\\|' : 0x2223 , '\\{' : 0x7b , '\\}' : 0x7d , '\\lbrace' : 0x7b , '\\rbrace' : 0x7d , '\\lparen' : 0x28 , '\\rparen' : 0x29 , '\\lbrack' : 0x5b , '\\rbrack' : 0x5d , '\\vert' : 0x2223 , '\\lvert' : 0x2223 , '\\mvert' : 0x2223 , '\\rvert' : 0x2223 , '\\Vert' : 0x2225 , '\\lVert' : 0x2225 , '\\mVert' : 0x2225 , '\\rVert' : 0x2225 , '\\parallel' : 0x2225 , '\\shortparallel' : 0x2225 , '\\langle' : 0x27e8 , '\\rangle' : 0x27e9 , '\\lfloor' : 0x230a , '\\rfloor' : 0x230b , '\\lceil' : 0x2308 , '\\rceil' : 0x2309 , '\\ulcorner' : 0x250c , '\\urcorner' : 0x2510 , '\\llcorner' : 0x2514 , '\\lrcorner' : 0x2518 , '\\lgroup' : 0x27ee , '\\rgroup' : 0x27ef , '\\lmoustache' : 0x23b0 , '\\rmoustache' : 0x23b1 , '\\surd' : 0x221a , } [ symbol ] ?? symbol . codePointAt ( 0 ) ! ) ; } /**
 * Makes a small delimiter. This is a delimiter that comes in the Main-Regular
 * font, but is restyled to either be in textstyle, scriptstyle, or
 * scriptscriptstyle.
 */ function makeSmallDelim ( delim , context , center , options ) { const text = new Box ( getSymbolValue ( delim ) , { fontFamily : 'Main-Regular' } ) ; const box = text . wrap ( context , options ) ; if ( center ) { box . setTop ( ( 1 - context . scalingFactor ) * AXIS_HEIGHT ) ; } return box ; } /**
 * Makes a large delimiter. This is a delimiter that comes in the Size1, Size2,
 * Size3, or Size4 fonts.
 */ function makeLargeDelim ( delim , size , center , parentContext , options ) { const context = new Context ( parentContext , options ?. style , 'textstyle' ) ; const result = new Box ( getSymbolValue ( delim ) , { fontFamily : 'Size' + size + '-Regular' , classes : 'ML__delim-size' + size , } ) . wrap ( context ) ; if ( center ) { result . setTop ( ( 1 - context . scalingFactor ) * AXIS_HEIGHT ) ; } return result ; } /**
 * Make a stacked delimiter out of a given delimiter, with the total height at
 * least `heightTotal`. This routine is mentioned on page 442 of the TeXbook.
 */ function makeStackedDelim ( delim , heightTotal , center , context , options ) { let top ; let middle ; let repeat ; let bottom ; top = repeat = bottom = getSymbolValue ( delim ) ; middle = null ; let fontFamily = 'Size1-Regular' ; if ( delim === '\\vert' || delim === '\\lvert' || delim === '\\rvert' || delim === '\\mvert' || delim === '\\mid' ) { repeat = top = bottom = 0x2223 ; } else if ( delim === '\\Vert' || delim === '\\lVert' || delim === '\\rVert' || delim === '\\mVert' || delim === '\\|' ) { repeat = top = bottom = 0x2225 ; } else if ( delim === '\\uparrow' ) { repeat = bottom = 0x23d0 ; } else if ( delim === '\\Uparrow' ) { repeat = bottom = 0x2016 ; } else if ( delim === '\\downarrow' ) { top = repeat = 0x23d0 ; } else if ( delim === '\\Downarrow' ) { top = repeat = 0x2016 ; } else if ( delim === '\\updownarrow' ) { top = 0x2191 ; repeat = 0x23d0 ; bottom = 0x2193 ; } else if ( delim === '\\Updownarrow' ) { top = 0x21d1 ; repeat = 0x2016 ; bottom = 0x21d3 ; } else if ( delim === '[' || delim === '\\lbrack' ) { top = 0x23a1 ; repeat = 0x23a2 ; bottom = 0x23a3 ; fontFamily = 'Size4-Regular' ; } else if ( delim === ']' || delim === '\\rbrack' ) { top = 0x23a4 ; repeat = 0x23a5 ; bottom = 0x23a6 ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\lfloor' || delim === '\u230a' ) { repeat = top = 0x23a2 ; bottom = 0x23a3 ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\lceil' || delim === '\u2308' ) { top = 0x23a1 ; repeat = bottom = 0x23a2 ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\rfloor' || delim === '\u230b' ) { repeat = top = 0x23a5 ; bottom = 0x23a6 ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\rceil' || delim === '\u2309' ) { top = 0x23a4 ; repeat = bottom = 0x23a5 ; fontFamily = 'Size4-Regular' ; } else if ( delim === '(' || delim === '\\lparen' ) { top = 0x239b ; repeat = 0x239c ; bottom = 0x239d ; fontFamily = 'Size4-Regular' ; } else if ( delim === ')' || delim === '\\rparen' ) { top = 0x239e ; repeat = 0x239f ; bottom = 0x23a0 ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\{' || delim === '\\lbrace' ) { top = 0x23a7 ; middle = 0x23a8 ; bottom = 0x23a9 ; repeat = 0x23aa ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\}' || delim === '\\rbrace' ) { top = 0x23ab ; middle = 0x23ac ; bottom = 0x23ad ; repeat = 0x23aa ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\lgroup' || delim === '\u27ee' ) { top = 0x23a7 ; bottom = 0x23a9 ; repeat = 0x23aa ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\rgroup' || delim === '\u27ef' ) { top = 0x23ab ; bottom = 0x23ad ; repeat = 0x23aa ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\lmoustache' || delim === '\u23b0' ) { top = 0x23a7 ; bottom = 0x23ad ; repeat = 0x23aa ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\rmoustache' || delim === '\u23b1' ) { top = 0x23ab ; bottom = 0x23a9 ; repeat = 0x23aa ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\surd' ) { top = 0xe001 ; bottom = 0x23b7 ; repeat = 0xe000 ; fontFamily = 'Size4-Regular' ; } else if ( delim === '\\ulcorner' ) { top = 0x250c ; repeat = bottom = 0x20 ; } else if ( delim === '\\urcorner' ) { top = 0x2510 ; repeat = bottom = 0x20 ; } else if ( delim === '\\llcorner' ) { bottom = 0x2514 ; repeat = top = 0x20 ; } else if ( delim === '\\lrcorner' ) { top = 0x2518 ; repeat = top = 0x20 ; } const topMetrics = getCharacterMetrics ( top , fontFamily ) ; const topHeightTotal = topMetrics . height + topMetrics . depth ; const repeatMetrics = getCharacterMetrics ( repeat , fontFamily ) ; const repeatHeightTotal = repeatMetrics . height + repeatMetrics . depth ; const bottomMetrics = getCharacterMetrics ( bottom , fontFamily ) ; const bottomHeightTotal = bottomMetrics . height + bottomMetrics . depth ; let middleHeightTotal = 0 ; let middleFactor = 1 ; if ( middle !== null ) { const middleMetrics = getCharacterMetrics ( middle , fontFamily ) ; middleHeightTotal = middleMetrics . height + middleMetrics . depth ; middleFactor = 2 ; } const minHeight = topHeightTotal + bottomHeightTotal + middleHeightTotal ; const repeatCount = Math . max ( 0 , Math . ceil ( ( heightTotal - minHeight ) / ( middleFactor * repeatHeightTotal ) ) ) ; const realHeightTotal = minHeight + repeatCount * middleFactor * repeatHeightTotal ; let axisHeight = AXIS_HEIGHT ; if ( center ) { axisHeight = axisHeight * context . scalingFactor ; } const depth = realHeightTotal / 2 - axisHeight ; const OVERLAP = 0.008 ; const stack = [ ] ; stack . push ( { box : new Box ( bottom , { fontFamily } ) } ) ; stack . push ( - OVERLAP ) ; const repeatBox = new Box ( repeat , { fontFamily } ) ; if ( middle === null ) { for ( let i = 0 ; i < repeatCount ; i ++ ) { stack . push ( { box : repeatBox } ) ; } } else { for ( let i = 0 ; i < repeatCount ; i ++ ) { stack . push ( { box : repeatBox } ) ; } stack . push ( - OVERLAP ) ; stack . push ( { box : new Box ( middle , { fontFamily } ) } ) ; stack . push ( - OVERLAP ) ; for ( let i = 0 ; i < repeatCount ; i ++ ) { stack . push ( { box : repeatBox } ) ; } } stack . push ( - OVERLAP ) ; stack . push ( { box : new Box ( top , { fontFamily } ) } ) ; let sizeClass = '' ; if ( fontFamily === 'Size1-Regular' ) { sizeClass = ' delim-size1' ; } else if ( fontFamily === 'Size4-Regular' ) { sizeClass = ' delim-size4' ; } const inner = new VBox ( { bottom : depth , children : stack , } , { classes : sizeClass } ) ; const result = new Box ( inner , { ... ( options ?? { } ) , classes : ( options ?. classes ?? '' ) + ' ML__delim-mult' , } ) ; return result ; } const stackLargeDelimiters = new Set ( [ '(' , ')' , '\\lparen' , '\\rparen' , '[' , ']' , '\\lbrack' , '\\rbrack' , '\\{' , '\\}' , '\\lbrace' , '\\rbrace' , '\\lfloor' , '\\rfloor' , '\\lceil' , '\\rceil' , '\\surd' , '\u230a' , '\u230b' , '\u2308' , '\u2309' , ] ) ; const stackAlwaysDelimiters = new Set ( [ '\\uparrow' , '\\downarrow' , '\\updownarrow' , '\\Uparrow' , '\\Downarrow' , '\\Updownarrow' , '|' , '\\|' , '\\vert' , '\\Vert' , '\\lvert' , '\\rvert' , '\\lVert' , '\\rVert' , '\\mvert' , '\\mid' , '\\lgroup' , '\\rgroup' , '\\lmoustache' , '\\rmoustache' , '\u27ee' , '\u27ef' , '\u23b0' , '\u23b1' , ] ) ; const stackNeverDelimiters = new Set ( [ '<' , '>' , '\\langle' , '\\rangle' , '/' , '\\backslash' , '\\lt' , '\\gt' , ] ) ; const sizeToMaxHeight = [ 0 , 1.2 , 1.8 , 2.4 , 3 ] ; /**
 * Used to create a delimiter of a specific size, where `size` is 1, 2, 3, or 4.
 */ export function makeSizedDelim ( delim , size , context , options ) { if ( delim === undefined || delim === '.' ) { return makeNullDelimiter ( context , options . type ?? 'minner' , options . classes ) ; } if ( delim === '<' || delim === '\\lt' || delim === '\u27e8' ) { delim = '\\langle' ; } else if ( delim === '>' || delim === '\\gt' || delim === '\u27e9' ) { delim = '\\rangle' ; } if ( stackLargeDelimiters . has ( delim ) || stackNeverDelimiters . has ( delim ) ) { return makeLargeDelim ( delim , size , false , context , options ) ; } if ( stackAlwaysDelimiters . has ( delim ) ) { return makeStackedDelim ( delim , sizeToMaxHeight [ size ] , false , context , options ) ; } console . assert ( false , "Unknown delimiter '" + delim + "'" ) ; return null ; } interface DelimiterInfo { type : 'small' | 'large' | 'stack' ; mathstyle ? : MathstyleName ; size ? : 1 | 2 | 3 | 4 ; } const stackNeverDelimiterSequence = [ { type : 'small' , mathstyle : 'scriptscriptstyle' } , { type : 'small' , mathstyle : 'scriptstyle' } , { type : 'small' , mathstyle : 'textstyle' } , { type : 'large' , size : 1 } , { type : 'large' , size : 2 } , { type : 'large' , size : 3 } , { type : 'large' , size : 4 } , ] ; const stackAlwaysDelimiterSequence = [ { type : 'small' , mathstyle : 'scriptscriptstyle' } , { type : 'small' , mathstyle : 'scriptscriptstyle' } , { type : 'small' , mathstyle : 'textstyle' } , { type : 'stack' } , ] ; const stackLargeDelimiterSequence = [ { type : 'small' , mathstyle : 'scriptscriptstyle' } , { type : 'small' , mathstyle : 'scriptstyle' } , { type : 'small' , mathstyle : 'textstyle' } , { type : 'large' , size : 1 } , { type : 'large' , size : 2 } , { type : 'large' , size : 3 } , { type : 'large' , size : 4 } , { type : 'stack' } , ] ; function delimTypeToFont ( info ) { if ( info . type === 'small' ) { return 'Main-Regular' ; } if ( info . type === 'large' ) { return 'Size' + info . size + '-Regular' ; } console . assert ( info . type === 'stack' ) ; return 'Size4-Regular' ; } param delim function traverseSequence ( delim , height , sequence , context ) { const start = { '-4' : 0 , '-3' : 1 , '0' : 2 } [ context . mathstyle . sizeDelta ] ; for ( let i = start ; i < sequence . length ; i ++ ) { if ( sequence [ i ] . type === 'stack' ) { break ; } const metrics = getCharacterMetrics ( delim , delimTypeToFont ( sequence [ i ] ) ) ; if ( metrics . defaultMetrics ) { return { type : 'small' , mathstyle : 'scriptstyle' } ; } let heightDepth = metrics . height + metrics . depth ; if ( sequence [ i ] . type === 'small' ) { if ( sequence [ i ] . mathstyle === 'scriptscriptstyle' ) { heightDepth *= FONT_SCALE [ Math . max ( 1 , context . size - 2 ) ] ; } else if ( sequence [ i ] . mathstyle === 'scriptstyle' ) { heightDepth *= FONT_SCALE [ Math . max ( 1 , context . size - 1 ) ] ; } } if ( heightDepth > height ) { return sequence [ i ] ; } } return sequence [ sequence . length - 1 ] ; } /**
 * Make a delimiter of a given height+depth, with optional centering. Here, we
 * traverse the sequences, and create a delimiter that the sequence tells us to.
 */ export function makeCustomSizedDelim ( type , delim , height , center , context , options ? ) { if ( ! delim || delim . length === 0 || delim === '.' ) { return makeNullDelimiter ( context , type , type ) ; } if ( delim === '<' || delim === '\\lt' ) { delim = '\\langle' ; } else if ( delim === '>' || delim === '\\gt' ) { delim = '\\rangle' ; } let sequence ; if ( stackNeverDelimiters . has ( delim ) ) { sequence = stackNeverDelimiterSequence ; } else if ( stackLargeDelimiters . has ( delim ) ) { sequence = stackLargeDelimiterSequence ; } else { sequence = stackAlwaysDelimiterSequence ; } const delimType = traverseSequence ( getSymbolValue ( delim ) , height , sequence , context ) ; const delimContext = new Context ( context , options ?. style , delimType . mathstyle ) ; if ( delimType . type === 'small' ) { return makeSmallDelim ( delim , delimContext , center , { type , classes : 'ML__small-delim ' + ( options ?. classes ?? '' ) , } ) ; } if ( delimType . type === 'large' ) { return makeLargeDelim ( delim , delimType . size ! , center , delimContext , { ... options , type , } ) ; } console . assert ( delimType . type === 'stack' ) ; return makeStackedDelim ( delim , height , center , delimContext , { ... options , type , } ) ; } /**
 * Make a delimiter for use with `\left` and `\right`, given a height and depth
 * of an expression that the delimiters surround.
 * See tex.web:14994
 */ export function makeLeftRightDelim ( type , delim , height , depth , context , options ? ) { if ( delim === '.' ) { return makeNullDelimiter ( context , type , options ?. classes ) ; } const axisHeight = AXIS_HEIGHT * context . scalingFactor ; const delimiterFactor = 901 ; const delimiterExtend = 5 / PT_PER_EM ; const maxDistFromAxis = Math . max ( height - axisHeight , depth + axisHeight ) ; const totalHeight = Math . max ( ( maxDistFromAxis / 500 ) * delimiterFactor , 2 * maxDistFromAxis - delimiterExtend ) ; return makeCustomSizedDelim ( type , delim , totalHeight , true , context , options ) ; } export function makeNullDelimiter ( parentContext , type , classes ? ) { const context = new Context ( parentContext , undefined , 'textstyle' ) ; return new Box ( null , { classes : ' nulldelimiter ' + ( classes ?? '' ) , type , } ) . wrap ( context ) ; }