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