mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
148 lines
4.1 KiB
Rust
148 lines
4.1 KiB
Rust
use super::*;
|
|
|
|
const FRAC_AROUND: Em = Em::new(0.1);
|
|
|
|
/// # Fraction
|
|
/// 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(())
|
|
}
|