Under- and overbraces

This commit is contained in:
Laurenz 2023-01-22 13:31:44 +01:00
parent bcf20610fc
commit c5ef350cce
2 changed files with 126 additions and 0 deletions

123
library/src/math/braced.rs Normal file
View File

@ -0,0 +1,123 @@
use super::*;
const BRACED_GAP: Em = Em::new(0.3);
/// # 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<()> {
let gap = BRACED_GAP.scaled(ctx);
let body = ctx.layout_row(&self.body)?;
let glyph = GlyphFragment::new(ctx, '⏟');
let brace = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
let mut rows = vec![body, brace.into()];
ctx.style(ctx.style.for_subscript());
rows.extend(
self.annotation
.as_ref()
.map(|annotation| ctx.layout_row(annotation))
.transpose()?,
);
ctx.unstyle();
ctx.push(stack(ctx, rows, Align::Center, gap, 0));
Ok(())
}
}
/// # 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<()> {
let gap = BRACED_GAP.scaled(ctx);
let body = ctx.layout_row(&self.body)?;
let glyph = GlyphFragment::new(ctx, '⏞');
let brace = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
let mut rows = vec![];
ctx.style(ctx.style.for_superscript());
rows.extend(
self.annotation
.as_ref()
.map(|annotation| ctx.layout_row(annotation))
.transpose()?,
);
ctx.unstyle();
rows.push(brace.into());
rows.push(body);
let last = rows.len() - 1;
ctx.push(stack(ctx, rows, Align::Center, gap, last));
Ok(())
}
}

View File

@ -5,6 +5,7 @@ mod ctx;
mod accent; mod accent;
mod align; mod align;
mod atom; mod atom;
mod braced;
mod frac; mod frac;
mod group; mod group;
mod matrix; mod matrix;
@ -55,6 +56,8 @@ pub fn define(scope: &mut Scope) {
scope.def_func::<RootNode>("root"); scope.def_func::<RootNode>("root");
scope.def_func::<VecNode>("vec"); scope.def_func::<VecNode>("vec");
scope.def_func::<CasesNode>("cases"); scope.def_func::<CasesNode>("cases");
scope.def_func::<UnderbraceNode>("underbrace");
scope.def_func::<OverbraceNode>("overbrace");
scope.def_func::<BoldNode>("bold"); scope.def_func::<BoldNode>("bold");
scope.def_func::<ItalicNode>("italic"); scope.def_func::<ItalicNode>("italic");
scope.def_func::<SerifNode>("serif"); scope.def_func::<SerifNode>("serif");