Repository: typst/typst
File: crates/typst-layout/src/math/frac.rs (Rust)

Code before:
    // let vgap = vgap + axis / 2.0; // LuaTeX behavior

    let num_style = style_for_numerator(styles);
    let num_frame = ctx.layout_into_frame(num, styles.chain(&num_style))?;
    let num_size = num_frame.size();
    let denom_style = style_for_denominator(styles);
    let denom_frame = ctx.layout_into_frame(denom, styles.chain(&denom_style))?;
    let denom_size = denom_frame.size();

    let short_fall = DELIM_SHORT_FALL.resolve(styles);

    // Height of the fraction frame
    // We recalculate this value below if the slash glyph overflows
    let mut fraction_height = num_size.y + denom_size.y + vgap;

    // Build the slash glyph to calculate its size
    let mut slash_frag =
        ctx.layout_into_fragment(&SymbolElem::packed('\u{2044}').spanned(span), styles)?;
    let pre_stretch_height = slash_frag.size().y;
    slash_frag.stretch_vertical(ctx, fraction_height - short_fall);
    // If the standard slash was not stretchable, try the fraction slash
    if slash_frag.size().y == pre_stretch_height {
        slash_frag =
            ctx.layout_into_fragment(&SymbolElem::packed('/').spanned(span), styles)?;
        slash_frag.stretch_vertical(ctx, fraction_height - short_fall);
    }
    slash_frag.center_on_axis();
    let slash_frame = slash_frag.into_frame();

    // Adjust the fraction height if the slash overflows
    // Fraction width will be re-calculated later on after we adjusted the x values to avoid
    // overlap with the slash.
    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);

    // Reference points for all three objects, used to place them in the frame.
    let mut slash_center = Point::new(num_size.x + hgap / 2.0, fraction_height / 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);

    // Check for overlap with the slash glyph. We assume the slash is a straight line without
    // thickness that joins the upper right corner to the lower left corner of slash_frame.
    // Begin with the numerator
    let vec_num_slash = num_up_left + num_size.to_point() - slash_center;
    let mut extra_hgap = Point::zero();
    if vec_num_slash.x.to_raw() * slash_size.y.to_raw()
        + vec_num_slash.y.to_raw() * slash_size.x.to_raw()
        > 0.0
    {
        extra_hgap = Point::with_x(

Review comment (suggestion):
Checking the height after stretching against the `pre_stretch_height` could be a bit problematic: if the target height is the same size as the unstretched glyph, we will unnecessarily switch to the normal slash. 

I think we shouldn't bother with trying to do any fallback here. The example with the fraction slash U+2044 not being stretchable in STIX Two Math is a font issue (and has been fixed on the dev version).

Code after:
    let num_style = style_for_numerator(styles);
    let num_frame = ctx.layout_into_frame(num, styles.chain(&num_style))?;
    let num_size = num_frame.size();
    let denom_style = style_for_denominator(styles);
    let denom_frame = ctx.layout_into_frame(denom, styles.chain(&denom_style))?;
    let denom_size = denom_frame.size();

    let short_fall = DELIM_SHORT_FALL.resolve(styles);

    // Height of the fraction frame
    // We recalculate this value below if the slash glyph overflows
    let mut fraction_height = num_size.y + denom_size.y + vgap;

    // Build the slash glyph to calculate its size
    let mut slash_frag =
        ctx.layout_into_fragment(&SymbolElem::packed('\u{2044}').spanned(span), styles)?;
    slash_frag.stretch_vertical(ctx, fraction_height - short_fall);
    slash_frag.center_on_axis();
    let slash_frame = slash_frag.into_frame();

    // Adjust the fraction height if the slash overflows
    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);

    // Reference points for all three objects, used to place them in the frame.
    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);

    // Fraction width
    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);
    // We have to shift everything right to avoid going in the negatives for
    // the x coordinate
    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;

    // Build the final frame
    let mut fraction_frame = Frame::soft(Size::new(fraction_width, fraction_height));

    // Baseline (use axis height to center slash on the axis)
    fraction_frame.set_baseline(fraction_height / 2.0 + axis);

    // Numerator, Denominator, Slash
    fraction_frame.push_frame(num_up_left, num_frame);
    fraction_frame.push_frame(denom_up_left, denom_frame);