mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
318 lines
7.4 KiB
Rust
318 lines
7.4 KiB
Rust
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_aligned_frame(ctx, &points, align))
|
|
.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
|
|
}
|