use super::*; /// How much less high scaled delimiters can be than what they wrap. pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1); /// Scales delimiters. /// /// While matched delimiters scale by default, this can be used to scale /// unmatched delimiters and to control the delimiter scaling more precisely. /// /// ## Example /// ```example /// $ lr(]a, b/2]) $ /// $ lr(]sum_(x=1)^n] x, size: #50%) $ /// ``` /// /// Display: Left/Right /// Category: math #[element(LayoutMath)] pub struct LrElem { /// The size of the brackets, relative to the height of the wrapped content. /// /// Defaults to `{100%}`. pub size: Smart>, /// The delimited content, including the delimiters. #[required] #[parse( let mut body = Content::empty(); for (i, arg) in args.all::()?.into_iter().enumerate() { if i > 0 { body += TextElem::packed(','); } body += arg; } body )] pub body: Content, } impl LayoutMath for LrElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let mut body = self.body(); if let Some(elem) = body.to::() { if elem.size(ctx.styles()).is_auto() { body = elem.body(); } } let mut fragments = ctx.layout_fragments(&body)?; let axis = scaled!(ctx, axis_height); let max_extent = fragments .iter() .map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis)) .max() .unwrap_or_default(); let height = self .size(ctx.styles()) .unwrap_or(Rel::one()) .resolve(ctx.styles()) .relative_to(2.0 * max_extent); match fragments.as_mut_slice() { [one] => scale(ctx, one, height, None), [first, .., last] => { scale(ctx, first, height, Some(MathClass::Opening)); scale(ctx, last, height, Some(MathClass::Closing)); } _ => {} } ctx.extend(fragments); Ok(()) } } /// Scale a math fragment to a height. fn scale( ctx: &mut MathContext, fragment: &mut MathFragment, height: Abs, apply: Option, ) { if matches!( fragment.class(), Some(MathClass::Opening | MathClass::Closing | MathClass::Fence) ) { let glyph = match fragment { MathFragment::Glyph(glyph) => glyph.clone(), MathFragment::Variant(variant) => { GlyphFragment::new(ctx, variant.c, variant.span) } _ => return, }; let short_fall = DELIM_SHORT_FALL.scaled(ctx); *fragment = MathFragment::Variant(glyph.stretch_vertical(ctx, height, short_fall)); if let Some(class) = apply { fragment.set_class(class); } } } /// Floor an expression. /// /// ## Example /// ```example /// $ floor(x/2) $ /// ``` /// /// Display: Floor /// Category: math /// Returns: content #[func] pub fn floor( /// The expression to floor. body: Content, ) -> Value { delimited(body, '⌊', '⌋') } /// Ceil an expression. /// /// ## Example /// ```example /// $ ceil(x/2) $ /// ``` /// /// Display: Ceil /// Category: math /// Returns: content #[func] pub fn ceil( /// The expression to ceil. body: Content, ) -> Value { delimited(body, '⌈', '⌉') } /// Round an expression. /// /// ## Example /// ```example /// $ round(x/2) $ /// ``` /// /// Display: Round /// Category: math /// Returns: content #[func] pub fn round( /// The expression to round. body: Content, ) -> Value { delimited(body, '⌊', '⌉') } /// Take the absolute value of an expression. /// /// ## Example /// ```example /// $ abs(x/2) $ /// ``` /// /// /// Display: Abs /// Category: math /// Returns: content #[func] pub fn abs( /// The expression to take the absolute value of. body: Content, ) -> Value { delimited(body, '|', '|') } /// Take the norm of an expression. /// /// ## Example /// ```example /// $ norm(x/2) $ /// ``` /// /// Display: Norm /// Category: math /// Returns: content #[func] pub fn norm( /// The expression to take the norm of. body: Content, ) -> Value { delimited(body, '‖', '‖') } fn delimited(body: Content, left: char, right: char) -> Value { LrElem::new(Content::sequence([ TextElem::packed(left), body, TextElem::packed(right), ])) .pack() .into() }