use super::*; const FRAC_AROUND: Em = Em::new(0.1); /// A mathematical fraction. /// /// ## Example /// ```example /// $ 1/2 < (x+1)/2 $ /// $ ((x+1)) / 2 = frac(a, b) $ /// ``` /// /// ## Syntax /// This function also has dedicated syntax: Use a slash to turn neighbouring /// expressions into a fraction. Multiple atoms can be grouped into a single /// expression using round grouping parenthesis. Such parentheses are removed /// from the output, but you can nest multiple to force them. /// /// Display: Fraction /// Category: math #[element(LayoutMath)] pub struct FracElem { /// The fraction's numerator. #[required] pub num: Content, /// The fraction's denominator. #[required] pub denom: Content, } impl LayoutMath for FracElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout(ctx, &self.num(), &self.denom(), false, self.span()) } } /// A binomial expression. /// /// ## Example /// ```example /// $ binom(n, k) $ /// ``` /// /// Display: Binomial /// Category: math #[element(LayoutMath)] pub struct BinomElem { /// The binomial's upper index. #[required] pub upper: Content, /// The binomial's lower index. #[required] pub lower: Content, } impl LayoutMath for BinomElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout(ctx, &self.upper(), &self.lower(), true, self.span()) } } /// Layout a fraction or binomial. fn layout( ctx: &mut MathContext, num: &Content, denom: &Content, binom: bool, span: Span, ) -> SourceResult<()> { let short_fall = DELIM_SHORT_FALL.scaled(ctx); let axis = scaled!(ctx, axis_height); let thickness = scaled!(ctx, fraction_rule_thickness); let shift_up = scaled!( ctx, text: fraction_numerator_shift_up, display: fraction_numerator_display_style_shift_up, ); let shift_down = scaled!( ctx, text: fraction_denominator_shift_down, display: fraction_denominator_display_style_shift_down, ); let num_min = scaled!( ctx, text: fraction_numerator_gap_min, display: fraction_num_display_style_gap_min, ); let denom_min = scaled!( ctx, text: fraction_denominator_gap_min, display: fraction_denom_display_style_gap_min, ); ctx.style(ctx.style.for_numerator()); let num = ctx.layout_frame(num)?; ctx.unstyle(); ctx.style(ctx.style.for_denominator()); let denom = ctx.layout_frame(denom)?; ctx.unstyle(); let around = FRAC_AROUND.scaled(ctx); let num_gap = (shift_up - axis - num.descent()).max(num_min + thickness / 2.0); let denom_gap = (shift_down + axis - denom.ascent()).max(denom_min + thickness / 2.0); let line_width = num.width().max(denom.width()); let width = line_width + 2.0 * around; let height = num.height() + num_gap + thickness + denom_gap + denom.height(); let size = Size::new(width, height); let num_pos = Point::with_x((width - num.width()) / 2.0); let line_pos = Point::new((width - line_width) / 2.0, num.height() + num_gap + thickness / 2.0); let denom_pos = Point::new((width - denom.width()) / 2.0, height - denom.height()); let baseline = line_pos.y + axis; let mut frame = Frame::new(size); frame.set_baseline(baseline); frame.push_frame(num_pos, num); frame.push_frame(denom_pos, denom); if binom { ctx.push( GlyphFragment::new(ctx, '(', span).stretch_vertical(ctx, height, short_fall), ); ctx.push(FrameFragment::new(ctx, frame)); ctx.push( GlyphFragment::new(ctx, ')', span).stretch_vertical(ctx, height, short_fall), ); } else { frame.push( line_pos, FrameItem::Shape( Geometry::Line(Point::with_x(line_width)).stroked(Stroke { paint: TextElem::fill_in(ctx.styles()), thickness, }), span, ), ); ctx.push(FrameFragment::new(ctx, frame)); } Ok(()) }