summaryrefslogtreecommitdiff
path: root/crates/typst-layout/src/math/accent.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/accent.rs
parentb8034a343831e8609aec2ec81eb7eeda57aa5d81 (diff)
Split out four new crates (#5302)
Diffstat (limited to 'crates/typst-layout/src/math/accent.rs')
-rw-r--r--crates/typst-layout/src/math/accent.rs75
1 files changed, 75 insertions, 0 deletions
diff --git a/crates/typst-layout/src/math/accent.rs b/crates/typst-layout/src/math/accent.rs
new file mode 100644
index 00000000..9fa7a5a0
--- /dev/null
+++ b/crates/typst-layout/src/math/accent.rs
@@ -0,0 +1,75 @@
+use typst_library::diag::SourceResult;
+use typst_library::foundations::{Packed, StyleChain};
+use typst_library::layout::{Em, Frame, Point, Rel, Size};
+use typst_library::math::{Accent, AccentElem};
+
+use super::{
+ scaled_font_size, style_cramped, FrameFragment, GlyphFragment, MathContext,
+ MathFragment,
+};
+
+/// How much the accent can be shorter than the base.
+const ACCENT_SHORT_FALL: Em = Em::new(0.5);
+
+/// Lays out an [`AccentElem`].
+#[typst_macros::time(name = "math.accent", span = elem.span())]
+pub fn layout_accent(
+ elem: &Packed<AccentElem>,
+ ctx: &mut MathContext,
+ styles: StyleChain,
+) -> SourceResult<()> {
+ let cramped = style_cramped();
+ let base = ctx.layout_into_fragment(elem.base(), styles.chain(&cramped))?;
+
+ // Preserve class to preserve automatic spacing.
+ let base_class = base.class();
+ let base_attach = base.accent_attach();
+
+ let width = elem
+ .size(styles)
+ .unwrap_or(Rel::one())
+ .at(scaled_font_size(ctx, styles))
+ .relative_to(base.width());
+
+ // Forcing the accent to be at least as large as the base makes it too
+ // wide in many case.
+ let Accent(c) = elem.accent();
+ let glyph = GlyphFragment::new(ctx, styles, *c, elem.span());
+ let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size);
+ let variant = glyph.stretch_horizontal(ctx, width, short_fall);
+ let accent = variant.frame;
+ let accent_attach = variant.accent_attach;
+
+ // Descent is negative because the accent's ink bottom is above the
+ // baseline. Therefore, the default gap is the accent's negated descent
+ // minus the accent base height. Only if the base is very small, we need
+ // a larger gap so that the accent doesn't move too low.
+ let accent_base_height = scaled!(ctx, styles, accent_base_height);
+ let gap = -accent.descent() - base.height().min(accent_base_height);
+ let size = Size::new(base.width(), accent.height() + gap + base.height());
+ let accent_pos = Point::with_x(base_attach - accent_attach);
+ let base_pos = Point::with_y(accent.height() + gap);
+ let baseline = base_pos.y + base.ascent();
+ let base_italics_correction = base.italics_correction();
+ let base_text_like = base.is_text_like();
+
+ let base_ascent = match &base {
+ MathFragment::Frame(frame) => frame.base_ascent,
+ _ => base.ascent(),
+ };
+
+ let mut frame = Frame::soft(size);
+ frame.set_baseline(baseline);
+ frame.push_frame(accent_pos, accent);
+ frame.push_frame(base_pos, base.into_frame());
+ ctx.push(
+ FrameFragment::new(ctx, styles, frame)
+ .with_class(base_class)
+ .with_base_ascent(base_ascent)
+ .with_italics_correction(base_italics_correction)
+ .with_accent_attach(base_attach)
+ .with_text_like(base_text_like),
+ );
+
+ Ok(())
+}