Under- and overbraces
This commit is contained in:
parent
bcf20610fc
commit
c5ef350cce
123
library/src/math/braced.rs
Normal file
123
library/src/math/braced.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
Loading…
Reference in New Issue
Block a user