summaryrefslogtreecommitdiff
path: root/crates/typst-layout/src/math/underover.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-10-27 19:04:55 +0100
committerGitHub <noreply@github.com>2024-10-27 18:04:55 +0000
commitbe7cfc85d08c545abfac08098b7b33b4bd71f37e (patch)
treef4137fa2aaa57babae1f7603a9b2ed7e688f43d8 /crates/typst-layout/src/math/underover.rs
parentb8034a343831e8609aec2ec81eb7eeda57aa5d81 (diff)
Split out four new crates (#5302)
Diffstat (limited to 'crates/typst-layout/src/math/underover.rs')
-rw-r--r--crates/typst-layout/src/math/underover.rs327
1 files changed, 327 insertions, 0 deletions
diff --git a/crates/typst-layout/src/math/underover.rs b/crates/typst-layout/src/math/underover.rs
new file mode 100644
index 00000000..b1d4825b
--- /dev/null
+++ b/crates/typst-layout/src/math/underover.rs
@@ -0,0 +1,327 @@
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Content, Packed, StyleChain};
+use typst_library::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size};
+use typst_library::math::{
+ OverbraceElem, OverbracketElem, OverlineElem, OverparenElem, OvershellElem,
+ UnderbraceElem, UnderbracketElem, UnderlineElem, UnderparenElem, UndershellElem,
+};
+use typst_library::text::TextElem;
+use typst_library::visualize::{FixedStroke, Geometry};
+use typst_syntax::Span;
+
+use super::{
+ scaled_font_size, stack, style_cramped, style_for_subscript, style_for_superscript,
+ FrameFragment, GlyphFragment, LeftRightAlternator, MathContext, MathRun,
+};
+
+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- and overlines.
+enum Position {
+ Under,
+ Over,
+}
+
+/// Lays out an [`UnderlineElem`].
+#[typst_macros::time(name = "math.underline", span = elem.span())]
+pub fn layout_underline(
+ elem: &Packed<UnderlineElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverline(ctx, styles, elem.body(), elem.span(), Position::Under)
+}
+
+/// Lays out an [`OverlineElem`].
+#[typst_macros::time(name = "math.overline", span = elem.span())]
+pub fn layout_overline(
+ elem: &Packed<OverlineElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverline(ctx, styles, elem.body(), elem.span(), Position::Over)
+}
+
+/// Lays out an [`UnderbraceElem`].
+#[typst_macros::time(name = "math.underbrace", span = elem.span())]
+pub fn layout_underbrace(
+ elem: &Packed<UnderbraceElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏟',
+ BRACE_GAP,
+ Position::Under,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`OverbraceElem`].
+#[typst_macros::time(name = "math.overbrace", span = elem.span())]
+pub fn layout_overbrace(
+ elem: &Packed<OverbraceElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏞',
+ BRACE_GAP,
+ Position::Over,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`UnderbracketElem`].
+#[typst_macros::time(name = "math.underbracket", span = elem.span())]
+pub fn layout_underbracket(
+ elem: &Packed<UnderbracketElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⎵',
+ BRACKET_GAP,
+ Position::Under,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`OverbracketElem`].
+#[typst_macros::time(name = "math.overbracket", span = elem.span())]
+pub fn layout_overbracket(
+ elem: &Packed<OverbracketElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⎴',
+ BRACKET_GAP,
+ Position::Over,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`UnderparenElem`].
+#[typst_macros::time(name = "math.underparen", span = elem.span())]
+pub fn layout_underparen(
+ elem: &Packed<UnderparenElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏝',
+ PAREN_GAP,
+ Position::Under,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`OverparenElem`].
+#[typst_macros::time(name = "math.overparen", span = elem.span())]
+pub fn layout_overparen(
+ elem: &Packed<OverparenElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏜',
+ PAREN_GAP,
+ Position::Over,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`UndershellElem`].
+#[typst_macros::time(name = "math.undershell", span = elem.span())]
+pub fn layout_undershell(
+ elem: &Packed<UndershellElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏡',
+ SHELL_GAP,
+ Position::Under,
+ elem.span(),
+ )
+}
+
+/// Lays out an [`OvershellElem`].
+#[typst_macros::time(name = "math.overshell", span = elem.span())]
+pub fn layout_overshell(
+ elem: &Packed<OvershellElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ layout_underoverspreader(
+ ctx,
+ styles,
+ elem.body(),
+ &elem.annotation(styles),
+ '⏠',
+ SHELL_GAP,
+ Position::Over,
+ elem.span(),
+ )
+}
+
+/// layout under- or overlined content.
+fn layout_underoverline(
+ ctx: &mut MathContext,
+ styles: StyleChain,
+ body: &Content,
+ span: Span,
+ position: Position,
+) -> SourceResult<()> {
+ let (extra_height, content, line_pos, content_pos, baseline, bar_height, line_adjust);
+ 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);
+ extra_height = sep + bar_height + gap;
+
+ content = ctx.layout_into_fragment(body, styles)?;
+
+ line_pos = Point::with_y(content.height() + gap + bar_height / 2.0);
+ content_pos = Point::zero();
+ baseline = content.ascent();
+ line_adjust = -content.italics_correction();
+ }
+ 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);
+ extra_height = sep + bar_height + gap;
+
+ let cramped = style_cramped();
+ content = ctx.layout_into_fragment(body, styles.chain(&cramped))?;
+
+ line_pos = Point::with_y(sep + bar_height / 2.0);
+ content_pos = Point::with_y(extra_height);
+ baseline = content.ascent() + extra_height;
+ line_adjust = Abs::zero();
+ }
+ }
+
+ let width = content.width();
+ let height = content.height() + extra_height;
+ let size = Size::new(width, height);
+ let line_width = width + line_adjust;
+
+ let content_class = content.class();
+ let content_is_text_like = content.is_text_like();
+ let content_italics_correction = content.italics_correction();
+ let mut frame = Frame::soft(size);
+ frame.set_baseline(baseline);
+ frame.push_frame(content_pos, content.into_frame());
+ frame.push(
+ line_pos,
+ FrameItem::Shape(
+ Geometry::Line(Point::with_x(line_width)).stroked(FixedStroke {
+ paint: TextElem::fill_in(styles).as_decoration(),
+ thickness: bar_height,
+ ..FixedStroke::default()
+ }),
+ span,
+ ),
+ );
+
+ ctx.push(
+ FrameFragment::new(ctx, styles, frame)
+ .with_class(content_class)
+ .with_text_like(content_is_text_like)
+ .with_italics_correction(content_italics_correction),
+ );
+
+ Ok(())
+}
+
+/// Layout an over- or underbrace-like object.
+#[allow(clippy::too_many_arguments)]
+fn layout_underoverspreader(
+ ctx: &mut MathContext,
+ styles: StyleChain,
+ body: &Content,
+ annotation: &Option<Content>,
+ c: char,
+ gap: Em,
+ position: Position,
+ span: Span,
+) -> SourceResult<()> {
+ let font_size = scaled_font_size(ctx, styles);
+ let gap = gap.at(font_size);
+ let body = ctx.layout_into_run(body, styles)?;
+ let body_class = body.class();
+ let body = body.into_fragment(ctx, styles);
+ let glyph = GlyphFragment::new(ctx, styles, c, span);
+ let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
+
+ 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
+ }
+ };
+
+ let frame = stack(
+ rows,
+ FixedAlignment::Center,
+ gap,
+ baseline,
+ LeftRightAlternator::Right,
+ None,
+ );
+ ctx.push(FrameFragment::new(ctx, styles, frame).with_class(body_class));
+
+ Ok(())
+}