From 810491c9d31b614a435020f888fbd380e8e039a1 Mon Sep 17 00:00:00 2001 From: mkorje Date: Mon, 5 Aug 2024 11:28:17 +0000 Subject: [PATCH] Add missing under/over-delimiters (#4637) --- crates/typst/src/math/mod.rs | 4 + crates/typst/src/math/underover.rs | 208 ++++++++++++++++++++++------ docs/reference/groups.yml | 4 + tests/ref/math-underover-parens.png | Bin 0 -> 726 bytes tests/ref/math-underover-shells.png | Bin 0 -> 551 bytes tests/suite/math/underover.typ | 14 ++ 6 files changed, 191 insertions(+), 39 deletions(-) create mode 100644 tests/ref/math-underover-parens.png create mode 100644 tests/ref/math-underover-shells.png 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 0000000000000000000000000000000000000000..a461ee078021748637971894249a2e8044df6146 GIT binary patch literal 726 zcmV;{0xA88P)Bh@VoiIkLP)Ko`;7lg~!1h z%)uO-3E15{2Ve()e5eZuc#ur8NMEn@vIIk+1Rw%@I^2$9^GGIH{5AOlEW#Vg8l?`H znDQwHTa<1jqKg+dP8Za27Gb}q+e2efua9|*3QstOe;*w$`U$V?Jxj2+OF$>Odi?M~ zbF$U*K_aF)>%Uq|vpTE&I7>8;Qaq2#X_pYzR}>fI=jG<`c|6`_KL1jD;OFG#2i@qqsis8uj%Ke$3z`9r9~y}E@oj>)k+v? zB)n3kI?sExKU;89M&Y$lq3BZzJR*R`!9J|Pm7_%fPKv6dC?7zQcTsl8J`77`Vi=c5 z$|RES0Azi*3D#lnOxx!jb4sh5jHXKVVOpT+lOYAB#P_B$JF7Pn|G<;b{p_5%CMNyd zDU*3vPT+qD4Om!)vGAh0b9Q;_FoHya2i7Io+v<0YOtji6ZY;l5Sa>a-t2gdEZk6wI zR~Y8t{eGmEXpC7HjYuy#H`8!XqO!jhw?xOKeW$>%+%&VOF;tEA4%&uVdZe8vidAio zpyT9`G{P_}`x<#BHn-6Y8a3c;g#RU~f4|!8G>gfHZ z5EvSE21Y#qYJHYB2Osw9)|(dkwIa#n$EDVvW~oAGkP%Ks;iL6G$9u3+>&{m5a{yr* zVkGv=)W$ZsZQIHq)$ literal 0 HcmV?d00001 diff --git a/tests/ref/math-underover-shells.png b/tests/ref/math-underover-shells.png new file mode 100644 index 0000000000000000000000000000000000000000..fb327af69589a0082635205868c3ef5631a65ba6 GIT binary patch literal 551 zcmV+?0@(eDP)>~HbH*;w`HSioW#I93i8`pVfO@gKv?QmK-w=HLSW>;SOtKNFe8 zZiYVk=-+jM*znT#E#uL!B5?mpr$jCU#nL5o3B=*2i9Q)#-LsZ0KiA$jco`StCJy^e zRlEe7weB`QZiGf?4S$HkMq7