diff options
Diffstat (limited to 'crates/typst-layout/src/math/frac.rs')
| -rw-r--r-- | crates/typst-layout/src/math/frac.rs | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/crates/typst-layout/src/math/frac.rs b/crates/typst-layout/src/math/frac.rs new file mode 100644 index 00000000..50686333 --- /dev/null +++ b/crates/typst-layout/src/math/frac.rs @@ -0,0 +1,136 @@ +use typst_library::diag::SourceResult; +use typst_library::foundations::{Content, Packed, StyleChain}; +use typst_library::layout::{Em, Frame, FrameItem, Point, Size}; +use typst_library::math::{BinomElem, FracElem}; +use typst_library::text::TextElem; +use typst_library::visualize::{FixedStroke, Geometry}; +use typst_syntax::Span; + +use super::{ + scaled_font_size, style_for_denominator, style_for_numerator, FrameFragment, + GlyphFragment, MathContext, DELIM_SHORT_FALL, +}; + +const FRAC_AROUND: Em = Em::new(0.1); + +/// Lays out a [`FracElem`]. +#[typst_macros::time(name = "math.frac", span = elem.span())] +pub fn layout_frac( + elem: &Packed<FracElem>, + ctx: &mut MathContext, + styles: StyleChain, +) -> SourceResult<()> { + layout_frac_like( + ctx, + styles, + elem.num(), + std::slice::from_ref(elem.denom()), + false, + elem.span(), + ) +} + +/// Lays out a [`BinomElem`]. +#[typst_macros::time(name = "math.binom", span = elem.span())] +pub fn layout_binom( + elem: &Packed<BinomElem>, + ctx: &mut MathContext, + styles: StyleChain, +) -> SourceResult<()> { + layout_frac_like(ctx, styles, elem.upper(), elem.lower(), true, elem.span()) +} + +/// Layout a fraction or binomial. +fn layout_frac_like( + ctx: &mut MathContext, + styles: StyleChain, + num: &Content, + denom: &[Content], + binom: bool, + span: Span, +) -> SourceResult<()> { + let font_size = scaled_font_size(ctx, styles); + let short_fall = DELIM_SHORT_FALL.at(font_size); + let axis = scaled!(ctx, styles, axis_height); + let thickness = scaled!(ctx, styles, fraction_rule_thickness); + let shift_up = scaled!( + ctx, styles, + text: fraction_numerator_shift_up, + display: fraction_numerator_display_style_shift_up, + ); + let shift_down = scaled!( + ctx, styles, + text: fraction_denominator_shift_down, + display: fraction_denominator_display_style_shift_down, + ); + let num_min = scaled!( + ctx, styles, + text: fraction_numerator_gap_min, + display: fraction_num_display_style_gap_min, + ); + let denom_min = scaled!( + ctx, styles, + text: fraction_denominator_gap_min, + display: fraction_denom_display_style_gap_min, + ); + + let num_style = style_for_numerator(styles); + let num = ctx.layout_into_frame(num, styles.chain(&num_style))?; + + let denom_style = style_for_denominator(styles); + let denom = ctx.layout_into_frame( + &Content::sequence( + // Add a comma between each element. + denom.iter().flat_map(|a| [TextElem::packed(','), a.clone()]).skip(1), + ), + styles.chain(&denom_style), + )?; + + let around = FRAC_AROUND.at(font_size); + let num_gap = (shift_up - (axis + thickness / 2.0) - num.descent()).max(num_min); + let denom_gap = + (shift_down + (axis - thickness / 2.0) - denom.ascent()).max(denom_min); + + 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::soft(size); + frame.set_baseline(baseline); + frame.push_frame(num_pos, num); + frame.push_frame(denom_pos, denom); + + if binom { + let mut left = GlyphFragment::new(ctx, styles, '(', span) + .stretch_vertical(ctx, height, short_fall); + left.center_on_axis(ctx); + ctx.push(left); + ctx.push(FrameFragment::new(ctx, styles, frame)); + let mut right = GlyphFragment::new(ctx, styles, ')', span) + .stretch_vertical(ctx, height, short_fall); + right.center_on_axis(ctx); + ctx.push(right); + } else { + frame.push( + line_pos, + FrameItem::Shape( + Geometry::Line(Point::with_x(line_width)).stroked( + FixedStroke::from_pair( + TextElem::fill_in(styles).as_decoration(), + thickness, + ), + ), + span, + ), + ); + ctx.push(FrameFragment::new(ctx, styles, frame)); + } + + Ok(()) +} |
