diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-01-28 23:13:11 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-01-28 23:15:03 +0100 |
| commit | 76048a8ef45ac5892235f2e69cb7cb6c35a037e4 (patch) | |
| tree | b882d3d209ae50f8849a94b38e8945e0b8330f03 /library/src/math/stack.rs | |
| parent | 406de22ee5cd74dc6f67743bad4710415bb50c41 (diff) | |
Overline, Underline, Overbracket, Underbracket
Diffstat (limited to 'library/src/math/stack.rs')
| -rw-r--r-- | library/src/math/stack.rs | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/library/src/math/stack.rs b/library/src/math/stack.rs new file mode 100644 index 00000000..c8a1252c --- /dev/null +++ b/library/src/math/stack.rs @@ -0,0 +1,315 @@ +use super::*; + +const LINE_GAP: Em = Em::new(0.15); +const BRACE_GAP: Em = Em::new(0.25); +const BRACKET_GAP: Em = Em::new(0.25); + +/// # Underline +/// A horizontal line under content. +/// +/// ## Example +/// ``` +/// $ underline(1 + 2 + ... + 5) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content above the line. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct UnderlineNode(Content); + +#[node] +impl UnderlineNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl LayoutMath for UnderlineNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.0, &None, '\u{305}', LINE_GAP, false) + } +} + +/// # Overline +/// A horizontal line over content. +/// +/// ## Example +/// ``` +/// $ overline(1 + 2 + ... + 5) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content below the line. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct OverlineNode(Content); + +#[node] +impl OverlineNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl LayoutMath for OverlineNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.0, &None, '\u{332}', LINE_GAP, true) + } +} + +/// # Underbrace +/// A horizontal brace under content, with an optional annotation below. +/// +/// ## Example +/// ``` +/// $ underbrace(1 + 2 + ... + 5, "numbers") $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content above the brace. +/// +/// - annotation: Content (positional) +/// The optional content below the brace. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct UnderbraceNode { + /// The content above the brace. + pub body: Content, + /// The optional content below the brace. + pub annotation: Option<Content>, +} + +#[node] +impl UnderbraceNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + let body = args.expect("body")?; + let annotation = args.eat()?; + Ok(Self { body, annotation }.pack()) + } +} + +impl LayoutMath for UnderbraceNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.body, &self.annotation, '⏟', BRACE_GAP, false) + } +} + +/// # Overbrace +/// A horizontal brace over content, with an optional annotation above. +/// +/// ## Example +/// ``` +/// $ overbrace(1 + 2 + ... + 5, "numbers") $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content below the brace. +/// +/// - annotation: Content (positional) +/// The optional content above the brace. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct OverbraceNode { + /// The content below the brace. + pub body: Content, + /// The optional content above the brace. + pub annotation: Option<Content>, +} + +#[node] +impl OverbraceNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + let body = args.expect("body")?; + let annotation = args.eat()?; + Ok(Self { body, annotation }.pack()) + } +} + +impl LayoutMath for OverbraceNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.body, &self.annotation, '⏞', BRACE_GAP, true) + } +} + +/// # Underbracket +/// A horizontal bracket under content, with an optional annotation below. +/// +/// ## Example +/// ``` +/// $ underbracket(1 + 2 + ... + 5, "numbers") $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content above the bracket. +/// +/// - annotation: Content (positional) +/// The optional content below the bracket. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct UnderbracketNode { + /// The content above the bracket. + pub body: Content, + /// The optional content below the bracket. + pub annotation: Option<Content>, +} + +#[node] +impl UnderbracketNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + let body = args.expect("body")?; + let annotation = args.eat()?; + Ok(Self { body, annotation }.pack()) + } +} + +impl LayoutMath for UnderbracketNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.body, &self.annotation, '⎵', BRACKET_GAP, false) + } +} + +/// # Overbracket +/// A horizontal bracket over content, with an optional annotation above. +/// +/// ## Example +/// ``` +/// $ overbracket(1 + 2 + ... + 5, "numbers") $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content below the bracket. +/// +/// - annotation: Content (positional) +/// The optional content above the bracket. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct OverbracketNode { + /// The content below the bracket. + pub body: Content, + /// The optional content above the bracket. + pub annotation: Option<Content>, +} + +#[node] +impl OverbracketNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + let body = args.expect("body")?; + let annotation = args.eat()?; + Ok(Self { body, annotation }.pack()) + } +} + +impl LayoutMath for OverbracketNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.body, &self.annotation, '⎴', BRACKET_GAP, true) + } +} + +/// Layout an over- or underthing. +fn layout( + ctx: &mut MathContext, + body: &Content, + annotation: &Option<Content>, + c: char, + gap: Em, + reverse: bool, +) -> SourceResult<()> { + let gap = gap.scaled(ctx); + let body = ctx.layout_row(body)?; + let glyph = GlyphFragment::new(ctx, c); + let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero()); + + let mut rows = vec![body, stretched.into()]; + ctx.style(if reverse { + ctx.style.for_subscript() + } else { + ctx.style.for_superscript() + }); + rows.extend( + annotation + .as_ref() + .map(|annotation| ctx.layout_row(annotation)) + .transpose()?, + ); + ctx.unstyle(); + + let mut baseline = 0; + if reverse { + rows.reverse(); + baseline = rows.len() - 1; + } + + ctx.push(stack(ctx, rows, Align::Center, gap, baseline)); + + Ok(()) +} + +/// Stack rows on top of each other. +/// +/// Add a `gap` between each row and uses the baseline of the `baseline`th +/// row for the whole frame. +pub(super) fn stack( + ctx: &MathContext, + rows: Vec<MathRow>, + align: Align, + gap: Abs, + baseline: usize, +) -> Frame { + let mut width = Abs::zero(); + let mut height = rows.len().saturating_sub(1) as f64 * gap; + + let points = alignments(&rows); + let rows: Vec<_> = + rows.into_iter().map(|row| row.to_line_frame(ctx, &points)).collect(); + + for row in &rows { + height += row.height(); + width.set_max(row.width()); + } + + let mut y = Abs::zero(); + let mut frame = Frame::new(Size::new(width, height)); + + for (i, row) in rows.into_iter().enumerate() { + let x = align.position(width - row.width()); + let pos = Point::new(x, y); + if i == baseline { + frame.set_baseline(y + row.baseline()); + } + y += row.height() + gap; + frame.push_frame(pos, row); + } + + frame +} |
