diff --git a/crates/typst/src/math/mod.rs b/crates/typst/src/math/mod.rs index 04db9efb1..966945ce2 100644 --- a/crates/typst/src/math/mod.rs +++ b/crates/typst/src/math/mod.rs @@ -176,6 +176,10 @@ pub fn module() -> Module { math.define_elem::(); math.define_elem::(); math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); + math.define_elem::(); math.define_elem::(); math.define_elem::(); math.define_elem::(); diff --git a/crates/typst/src/math/underover.rs b/crates/typst/src/math/underover.rs index defa46c1c..68a399835 100644 --- a/crates/typst/src/math/underover.rs +++ b/crates/typst/src/math/underover.rs @@ -2,9 +2,9 @@ use crate::diag::SourceResult; use crate::foundations::{elem, Content, Packed, StyleChain}; use crate::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size}; use crate::math::{ - alignments, scaled_font_size, style_cramped, style_for_subscript, AlignmentResult, - FrameFragment, GlyphFragment, LayoutMath, LeftRightAlternator, MathContext, MathRun, - Scaled, + alignments, scaled_font_size, style_cramped, style_for_subscript, + style_for_superscript, AlignmentResult, FrameFragment, GlyphFragment, LayoutMath, + LeftRightAlternator, MathContext, MathRun, Scaled, }; use crate::syntax::Span; use crate::text::TextElem; @@ -12,11 +12,13 @@ use crate::visualize::{FixedStroke, Geometry}; const BRACE_GAP: Em = Em::new(0.25); const BRACKET_GAP: Em = Em::new(0.25); +const PAREN_GAP: Em = Em::new(0.25); +const SHELL_GAP: Em = Em::new(0.25); /// A marker to distinguish under- vs. overlines. -enum LineKind { - Over, +enum Position { Under, + Over, } /// A horizontal line under content. @@ -34,7 +36,7 @@ pub struct UnderlineElem { impl LayoutMath for Packed { #[typst_macros::time(name = "math.underline", span = self.span())] fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> { - layout_underoverline(ctx, styles, self.body(), self.span(), LineKind::Under) + layout_underoverline(ctx, styles, self.body(), self.span(), Position::Under) } } @@ -53,7 +55,7 @@ pub struct OverlineElem { impl LayoutMath for Packed { #[typst_macros::time(name = "math.overline", span = self.span())] fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> { - layout_underoverline(ctx, styles, self.body(), self.span(), LineKind::Over) + layout_underoverline(ctx, styles, self.body(), self.span(), Position::Over) } } @@ -63,11 +65,11 @@ fn layout_underoverline( styles: StyleChain, body: &Content, span: Span, - line: LineKind, + position: Position, ) -> SourceResult<()> { let (extra_height, content, line_pos, content_pos, baseline, bar_height); - match line { - LineKind::Under => { + match position { + Position::Under => { let sep = scaled!(ctx, styles, underbar_extra_descender); bar_height = scaled!(ctx, styles, underbar_rule_thickness); let gap = scaled!(ctx, styles, underbar_vertical_gap); @@ -79,7 +81,7 @@ fn layout_underoverline( content_pos = Point::zero(); baseline = content.ascent() } - LineKind::Over => { + Position::Over => { let sep = scaled!(ctx, styles, overbar_extra_ascender); bar_height = scaled!(ctx, styles, overbar_rule_thickness); let gap = scaled!(ctx, styles, overbar_vertical_gap); @@ -145,7 +147,7 @@ impl LayoutMath for Packed { &self.annotation(styles), '⏟', BRACE_GAP, - false, + Position::Under, self.span(), ) } @@ -177,7 +179,7 @@ impl LayoutMath for Packed { &self.annotation(styles), '⏞', BRACE_GAP, - true, + Position::Over, self.span(), ) } @@ -200,7 +202,7 @@ pub struct UnderbracketElem { } impl LayoutMath for Packed { - #[typst_macros::time(name = "math.underbrace", span = self.span())] + #[typst_macros::time(name = "math.underbracket", span = self.span())] fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> { layout_underoverspreader( ctx, @@ -209,7 +211,7 @@ impl LayoutMath for Packed { &self.annotation(styles), '⎵', BRACKET_GAP, - false, + Position::Under, self.span(), ) } @@ -241,7 +243,135 @@ impl LayoutMath for Packed { &self.annotation(styles), '⎴', BRACKET_GAP, - true, + Position::Over, + self.span(), + ) + } +} + +/// A horizontal parenthesis under content, with an optional annotation below. +/// +/// ```example +/// $ underparen(1 + 2 + ... + 5, "numbers") $ +/// ``` +#[elem(LayoutMath)] +pub struct UnderparenElem { + /// The content above the parenthesis. + #[required] + pub body: Content, + + /// The optional content below the parenthesis. + #[positional] + pub annotation: Option, +} + +impl LayoutMath for Packed { + #[typst_macros::time(name = "math.underparen", span = self.span())] + fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> { + layout_underoverspreader( + ctx, + styles, + self.body(), + &self.annotation(styles), + '⏝', + PAREN_GAP, + Position::Under, + self.span(), + ) + } +} + +/// A horizontal parenthesis over content, with an optional annotation above. +/// +/// ```example +/// $ overparen(1 + 2 + ... + 5, "numbers") $ +/// ``` +#[elem(LayoutMath)] +pub struct OverparenElem { + /// The content below the parenthesis. + #[required] + pub body: Content, + + /// The optional content above the parenthesis. + #[positional] + pub annotation: Option, +} + +impl LayoutMath for Packed { + #[typst_macros::time(name = "math.overparen", span = self.span())] + fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> { + layout_underoverspreader( + ctx, + styles, + self.body(), + &self.annotation(styles), + '⏜', + PAREN_GAP, + Position::Over, + self.span(), + ) + } +} + +/// A horizontal tortoise shell bracket under content, with an optional annotation below. +/// +/// ```example +/// $ undershell(1 + 2 + ... + 5, "numbers") $ +/// ``` +#[elem(LayoutMath)] +pub struct UndershellElem { + /// The content above the tortoise shell bracket. + #[required] + pub body: Content, + + /// The optional content below the tortoise shell bracket. + #[positional] + pub annotation: Option, +} + +impl LayoutMath for Packed { + #[typst_macros::time(name = "math.undershell", span = self.span())] + fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> { + layout_underoverspreader( + ctx, + styles, + self.body(), + &self.annotation(styles), + '⏡', + SHELL_GAP, + Position::Under, + self.span(), + ) + } +} + +/// A horizontal tortoise shell bracket over content, with an optional annotation above. +/// +/// ```example +/// $ overshell(1 + 2 + ... + 5, "numbers") $ +/// ``` +#[elem(LayoutMath)] +pub struct OvershellElem { + /// The content below the tortoise shell bracket. + #[required] + pub body: Content, + + /// The optional content above the tortoise shell bracket. + #[positional] + pub annotation: Option, +} + +impl LayoutMath for Packed { + #[typst_macros::time(name = "math.overshell", span = self.span())] + fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> { + layout_underoverspreader( + ctx, + styles, + self.body(), + &self.annotation(styles), + '⏠', + SHELL_GAP, + Position::Over, self.span(), ) } @@ -256,7 +386,7 @@ fn layout_underoverspreader( annotation: &Option, c: char, gap: Em, - reverse: bool, + position: Position, span: Span, ) -> SourceResult<()> { let font_size = scaled_font_size(ctx, styles); @@ -267,30 +397,30 @@ fn layout_underoverspreader( let glyph = GlyphFragment::new(ctx, styles, c, span); let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero()); - let mut rows = vec![MathRun::new(vec![body]), stretched.into()]; - - let (sup_style, sub_style); - let row_styles = if reverse { - sup_style = style_for_subscript(styles); - styles.chain(&sup_style) - } else { - sub_style = style_for_subscript(styles); - styles.chain(&sub_style) + let mut rows = vec![]; + let baseline = match position { + Position::Under => { + rows.push(MathRun::new(vec![body])); + rows.push(stretched.into()); + if let Some(annotation) = annotation { + let under_style = style_for_subscript(styles); + let annotation_styles = styles.chain(&under_style); + rows.push(ctx.layout_into_run(annotation, annotation_styles)?); + } + 0 + } + Position::Over => { + if let Some(annotation) = annotation { + let over_style = style_for_superscript(styles); + let annotation_styles = styles.chain(&over_style); + rows.push(ctx.layout_into_run(annotation, annotation_styles)?); + } + rows.push(stretched.into()); + rows.push(MathRun::new(vec![body])); + rows.len() - 1 + } }; - rows.extend( - annotation - .as_ref() - .map(|annotation| ctx.layout_into_run(annotation, row_styles)) - .transpose()?, - ); - - let mut baseline = 0; - if reverse { - rows.reverse(); - baseline = rows.len() - 1; - } - let frame = stack( rows, FixedAlignment::Center, diff --git a/docs/reference/groups.yml b/docs/reference/groups.yml index 0cbf656af..f957051cd 100644 --- a/docs/reference/groups.yml +++ b/docs/reference/groups.yml @@ -46,6 +46,10 @@ "overbrace", "underbracket", "overbracket", + "underparen", + "overparen", + "undershell", + "overshell", ] details: | Delimiters above or below parts of an equation. diff --git a/tests/ref/math-underover-parens.png b/tests/ref/math-underover-parens.png new file mode 100644 index 000000000..a461ee078 Binary files /dev/null and b/tests/ref/math-underover-parens.png differ diff --git a/tests/ref/math-underover-shells.png b/tests/ref/math-underover-shells.png new file mode 100644 index 000000000..fb327af69 Binary files /dev/null and b/tests/ref/math-underover-shells.png differ diff --git a/tests/suite/math/underover.typ b/tests/suite/math/underover.typ index 0768bf737..5749d9fad 100644 --- a/tests/suite/math/underover.typ +++ b/tests/suite/math/underover.typ @@ -19,3 +19,17 @@ $ x = overbracket( $ underbracket([1, 2/3], "relevant stuff") arrow.l.r.double.long overbracket([4/5,6], "irrelevant stuff") $ + +--- math-underover-parens --- +// Test parentheses. +$ overparen( + underparen(x + y, "long comment"), + 1 + 2 + ... + 5 +) $ + +--- math-underover-shells --- +// Test tortoise shell brackets. +$ undershell( + 1 + overshell(2 + ..., x + y), + "all stuff" +) $