summaryrefslogtreecommitdiff
path: root/crates/typst-layout/src/math/frac.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-layout/src/math/frac.rs')
-rw-r--r--crates/typst-layout/src/math/frac.rs136
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(())
+}