use typst_library::diag::SourceResult;
use typst_library::foundations::{Resolve, StyleChain};
use typst_library::layout::{Abs, Axis, Frame, FrameItem, Point, Size};
use typst_library::math::MathSize;
use typst_library::math::ir::{FractionItem, MathProperties, SkewedFractionItem};
use typst_library::text::TextElem;
use typst_library::visualize::{FixedStroke, Geometry};
use super::MathContext;
use super::fragment::FrameFragment;
#[typst_macros::time(name = "math fraction layout", span = props.span)]
pub fn layout_fraction(
item: &FractionItem,
ctx: &mut MathContext,
styles: StyleChain,
props: &MathProperties,
) -> SourceResult<()> {
let num = ctx.layout_into_fragment(&item.numerator, styles)?.into_frame();
let denom = ctx.layout_into_fragment(&item.denominator, styles)?.into_frame();
let constants = ctx.font().math();
let size = styles.resolve(TextElem::size);
let math_size = props.size;
let frame = if item.line {
let axis = constants.axis_height.at(size);
let thickness = constants.fraction_rule_thickness.at(size);
let shift_up = match math_size {
MathSize::Display => constants.fraction_numerator_display_style_shift_up,
_ => constants.fraction_numerator_shift_up,
}
.at(size);
let shift_down = match math_size {
MathSize::Display => constants.fraction_denominator_display_style_shift_down,
_ => constants.fraction_denominator_shift_down,
}
.at(size);
let num_min = match math_size {
MathSize::Display => constants.fraction_num_display_style_gap_min,
_ => constants.fraction_numerator_gap_min,
}
.at(size);
let denom_min = match math_size {
MathSize::Display => constants.fraction_denom_display_style_gap_min,
_ => constants.fraction_denominator_gap_min,
}
.at(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 * item.padding.at(size);
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);
let text_fill = styles.get_ref(TextElem::fill).as_decoration();
let line = match styles.get_ref(TextElem::stroke) {
Some(stroke) => Geometry::Rect(Size::new(line_width, thickness))
.filled_and_stroked(
text_fill.clone(),
stroke.clone().resolve(styles).unwrap_or_default(),
),
None => Geometry::Line(Point::with_x(line_width))
.stroked(FixedStroke::from_pair(text_fill, thickness)),
};
frame.push(line_pos, FrameItem::Shape(line, props.span));
frame
} else {
let shift_up = match math_size {
MathSize::Display => constants.stack_top_display_style_shift_up,
_ => constants.stack_top_shift_up,
}
.at(size);
let shift_down = match math_size {
MathSize::Display => constants.stack_bottom_display_style_shift_down,
_ => constants.stack_bottom_shift_down,
}
.at(size);
let gap_min = match math_size {
MathSize::Display => constants.stack_display_style_gap_min,
_ => constants.stack_gap_min,
}
.at(size);
let gap = (shift_up - num.descent()) + (shift_down - denom.ascent());
let width = num.width().max(denom.width()) + 2.0 * item.padding.at(size);
let height = num.height() + gap.max(gap_min) + denom.height();
let size = Size::new(width, height);
let num_pos = Point::with_x((width - num.width()) / 2.0);
let denom_pos =
Point::new((width - denom.width()) / 2.0, height - denom.height());
let baseline = num.ascent() + shift_up + (gap_min - gap).max(Abs::zero()) / 2.0;
let mut frame = Frame::soft(size);
frame.set_baseline(baseline);
frame.push_frame(num_pos, num);
frame.push_frame(denom_pos, denom);
frame
};
ctx.push(FrameFragment::new(props, styles, frame));
Ok(())
}
#[typst_macros::time(name = "math skewed fraction layout", span = props.span)]
pub fn layout_skewed_fraction(
item: &SkewedFractionItem,
ctx: &mut MathContext,
styles: StyleChain,
props: &MathProperties,
) -> SourceResult<()> {
let constants = ctx.font().math();
let size = styles.resolve(TextElem::size);
let vgap = constants.skewed_fraction_vertical_gap.at(size);
let hgap = constants.skewed_fraction_horizontal_gap.at(size);
let axis = constants.axis_height.at(size);
let num_frame = ctx.layout_into_fragment(&item.numerator, styles)?.into_frame();
let num_size = num_frame.size();
let denom_frame = ctx.layout_into_fragment(&item.denominator, styles)?.into_frame();
let denom_size = denom_frame.size();
let mut fraction_height = num_size.y + denom_size.y + vgap;
item.slash.set_stretch_relative_to(fraction_height, Axis::Y);
let slash_frag = ctx.layout_into_fragment(&item.slash, styles)?;
let slash_frame = slash_frag.into_frame();
let slash_size = slash_frame.size();
let vertical_offset = Abs::zero().max(slash_size.y - fraction_height) / 2.0;
fraction_height.set_max(slash_size.y);
let mut slash_up_left = Point::new(num_size.x + hgap / 2.0, fraction_height / 2.0)
- slash_size.to_point() / 2.0;
let mut num_up_left = Point::with_y(vertical_offset);
let mut denom_up_left = num_up_left + num_size.to_point() + Point::new(hgap, vgap);
let fraction_width = (denom_up_left.x + denom_size.x)
.max(slash_up_left.x + slash_size.x)
+ Abs::zero().max(-slash_up_left.x);
let horizontal_offset = Point::with_x(Abs::zero().max(-slash_up_left.x));
slash_up_left += horizontal_offset;
num_up_left += horizontal_offset;
denom_up_left += horizontal_offset;
let mut fraction_frame = Frame::soft(Size::new(fraction_width, fraction_height));
fraction_frame.set_baseline(fraction_height / 2.0 + axis);
fraction_frame.push_frame(num_up_left, num_frame);
fraction_frame.push_frame(denom_up_left, denom_frame);
fraction_frame.push_frame(slash_up_left, slash_frame);
ctx.push(FrameFragment::new(props, styles, fraction_frame));
Ok(())
}