summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst-layout/src/math/accent.rs59
-rw-r--r--crates/typst-layout/src/math/attach.rs6
-rw-r--r--crates/typst-layout/src/math/fragment.rs33
-rw-r--r--crates/typst-layout/src/math/stretch.rs2
-rw-r--r--crates/typst-library/src/math/accent.rs13
-rw-r--r--tests/ref/math-accent-bottom-high-base.pngbin0 -> 572 bytes
-rw-r--r--tests/ref/math-accent-bottom-sized.pngbin0 -> 382 bytes
-rw-r--r--tests/ref/math-accent-bottom-subscript.pngbin0 -> 417 bytes
-rw-r--r--tests/ref/math-accent-bottom-wide-base.pngbin0 -> 359 bytes
-rw-r--r--tests/ref/math-accent-bottom.pngbin0 -> 622 bytes
-rw-r--r--tests/ref/math-accent-nested.pngbin0 -> 537 bytes
-rw-r--r--tests/suite/math/accent.typ28
12 files changed, 108 insertions, 33 deletions
diff --git a/crates/typst-layout/src/math/accent.rs b/crates/typst-layout/src/math/accent.rs
index 73d82101..53dfdf05 100644
--- a/crates/typst-layout/src/math/accent.rs
+++ b/crates/typst-layout/src/math/accent.rs
@@ -1,7 +1,7 @@
use typst_library::diag::SourceResult;
use typst_library::foundations::{Packed, StyleChain};
use typst_library::layout::{Em, Frame, Point, Size};
-use typst_library::math::{Accent, AccentElem};
+use typst_library::math::AccentElem;
use super::{style_cramped, FrameFragment, GlyphFragment, MathContext, MathFragment};
@@ -18,8 +18,11 @@ pub fn layout_accent(
let cramped = style_cramped();
let mut base = ctx.layout_into_fragment(&elem.base, styles.chain(&cramped))?;
- // Try to replace a glyph with its dotless variant.
- if elem.dotless(styles) {
+ let accent = elem.accent;
+ let top_accent = !accent.is_bottom();
+
+ // Try to replace base glyph with its dotless variant.
+ if top_accent && elem.dotless(styles) {
if let MathFragment::Glyph(glyph) = &mut base {
glyph.make_dotless_form(ctx);
}
@@ -29,41 +32,54 @@ pub fn layout_accent(
let base_class = base.class();
let base_attach = base.accent_attach();
- let width = elem.size(styles).relative_to(base.width());
-
- let Accent(c) = elem.accent;
- let mut glyph = GlyphFragment::new(ctx, styles, c, elem.span());
+ let mut glyph = GlyphFragment::new(ctx, styles, accent.0, elem.span());
- // Try to replace accent glyph with flattened variant.
- let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height);
- if base.ascent() > flattened_base_height {
- glyph.make_flattened_accent_form(ctx);
+ // Try to replace accent glyph with its flattened variant.
+ if top_accent {
+ let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height);
+ if base.ascent() > flattened_base_height {
+ glyph.make_flattened_accent_form(ctx);
+ }
}
// Forcing the accent to be at least as large as the base makes it too
// wide in many case.
+ let width = elem.size(styles).relative_to(base.width());
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;
+ let accent_attach = variant.accent_attach.0;
+
+ let (gap, accent_pos, base_pos) = if top_accent {
+ // 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.ascent().min(accent_base_height);
+ let accent_pos = Point::with_x(base_attach.0 - accent_attach);
+ let base_pos = Point::with_y(accent.height() + gap);
+ (gap, accent_pos, base_pos)
+ } else {
+ let gap = -accent.ascent();
+ let accent_pos = Point::new(base_attach.1 - accent_attach, base.height() + gap);
+ let base_pos = Point::zero();
+ (gap, accent_pos, base_pos)
+ };
- // 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.ascent().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 base_descent = match &base {
+ MathFragment::Frame(frame) => frame.base_descent,
+ _ => base.descent(),
+ };
let mut frame = Frame::soft(size);
frame.set_baseline(baseline);
@@ -73,6 +89,7 @@ pub fn layout_accent(
FrameFragment::new(styles, frame)
.with_class(base_class)
.with_base_ascent(base_ascent)
+ .with_base_descent(base_descent)
.with_italics_correction(base_italics_correction)
.with_accent_attach(base_attach)
.with_text_like(base_text_like),
diff --git a/crates/typst-layout/src/math/attach.rs b/crates/typst-layout/src/math/attach.rs
index e1d7d7c9..90aad941 100644
--- a/crates/typst-layout/src/math/attach.rs
+++ b/crates/typst-layout/src/math/attach.rs
@@ -434,9 +434,13 @@ fn compute_script_shifts(
}
if bl.is_some() || br.is_some() {
+ let descent = match &base {
+ MathFragment::Frame(frame) => frame.base_descent,
+ _ => base.descent(),
+ };
shift_down = shift_down
.max(sub_shift_down)
- .max(if is_text_like { Abs::zero() } else { base.descent() + sub_drop_min })
+ .max(if is_text_like { Abs::zero() } else { descent + sub_drop_min })
.max(measure!(bl, ascent) - sub_top_max)
.max(measure!(br, ascent) - sub_top_max);
}
diff --git a/crates/typst-layout/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs
index 59858a9c..85101c48 100644
--- a/crates/typst-layout/src/math/fragment.rs
+++ b/crates/typst-layout/src/math/fragment.rs
@@ -164,12 +164,12 @@ impl MathFragment {
}
}
- pub fn accent_attach(&self) -> Abs {
+ pub fn accent_attach(&self) -> (Abs, Abs) {
match self {
Self::Glyph(glyph) => glyph.accent_attach,
Self::Variant(variant) => variant.accent_attach,
Self::Frame(fragment) => fragment.accent_attach,
- _ => self.width() / 2.0,
+ _ => (self.width() / 2.0, self.width() / 2.0),
}
}
@@ -241,7 +241,7 @@ pub struct GlyphFragment {
pub ascent: Abs,
pub descent: Abs,
pub italics_correction: Abs,
- pub accent_attach: Abs,
+ pub accent_attach: (Abs, Abs),
pub font_size: Abs,
pub class: MathClass,
pub math_size: MathSize,
@@ -296,7 +296,7 @@ impl GlyphFragment {
descent: Abs::zero(),
limits: Limits::for_char(c),
italics_correction: Abs::zero(),
- accent_attach: Abs::zero(),
+ accent_attach: (Abs::zero(), Abs::zero()),
class,
span,
modifiers: FrameModifiers::get_in(styles),
@@ -328,8 +328,14 @@ impl GlyphFragment {
});
let mut width = advance.scaled(ctx, self.font_size);
- let accent_attach =
+
+ // The fallback for accents is half the width plus or minus the italics
+ // correction. This is similar to how top and bottom attachments are
+ // shifted. For bottom accents we do not use the accent attach of the
+ // base as it is meant for top acccents.
+ let top_accent_attach =
accent_attach(ctx, id, self.font_size).unwrap_or((width + italics) / 2.0);
+ let bottom_accent_attach = (width - italics) / 2.0;
let extended_shape = is_extended_shape(ctx, id);
if !extended_shape {
@@ -341,7 +347,7 @@ impl GlyphFragment {
self.ascent = bbox.y_max.scaled(ctx, self.font_size);
self.descent = -bbox.y_min.scaled(ctx, self.font_size);
self.italics_correction = italics;
- self.accent_attach = accent_attach;
+ self.accent_attach = (top_accent_attach, bottom_accent_attach);
self.extended_shape = extended_shape;
}
@@ -459,7 +465,7 @@ impl Debug for GlyphFragment {
pub struct VariantFragment {
pub c: char,
pub italics_correction: Abs,
- pub accent_attach: Abs,
+ pub accent_attach: (Abs, Abs),
pub frame: Frame,
pub font_size: Abs,
pub class: MathClass,
@@ -501,8 +507,9 @@ pub struct FrameFragment {
pub limits: Limits,
pub spaced: bool,
pub base_ascent: Abs,
+ pub base_descent: Abs,
pub italics_correction: Abs,
- pub accent_attach: Abs,
+ pub accent_attach: (Abs, Abs),
pub text_like: bool,
pub ignorant: bool,
}
@@ -510,6 +517,7 @@ pub struct FrameFragment {
impl FrameFragment {
pub fn new(styles: StyleChain, frame: Frame) -> Self {
let base_ascent = frame.ascent();
+ let base_descent = frame.descent();
let accent_attach = frame.width() / 2.0;
Self {
frame: frame.modified(&FrameModifiers::get_in(styles)),
@@ -519,8 +527,9 @@ impl FrameFragment {
limits: Limits::Never,
spaced: false,
base_ascent,
+ base_descent,
italics_correction: Abs::zero(),
- accent_attach,
+ accent_attach: (accent_attach, accent_attach),
text_like: false,
ignorant: false,
}
@@ -542,11 +551,15 @@ impl FrameFragment {
Self { base_ascent, ..self }
}
+ pub fn with_base_descent(self, base_descent: Abs) -> Self {
+ Self { base_descent, ..self }
+ }
+
pub fn with_italics_correction(self, italics_correction: Abs) -> Self {
Self { italics_correction, ..self }
}
- pub fn with_accent_attach(self, accent_attach: Abs) -> Self {
+ pub fn with_accent_attach(self, accent_attach: (Abs, Abs)) -> Self {
Self { accent_attach, ..self }
}
diff --git a/crates/typst-layout/src/math/stretch.rs b/crates/typst-layout/src/math/stretch.rs
index f45035e2..6157d0c5 100644
--- a/crates/typst-layout/src/math/stretch.rs
+++ b/crates/typst-layout/src/math/stretch.rs
@@ -278,7 +278,7 @@ fn assemble(
}
let accent_attach = match axis {
- Axis::X => frame.width() / 2.0,
+ Axis::X => (frame.width() / 2.0, frame.width() / 2.0),
Axis::Y => base.accent_attach,
};
diff --git a/crates/typst-library/src/math/accent.rs b/crates/typst-library/src/math/accent.rs
index e62b6387..f2c9168c 100644
--- a/crates/typst-library/src/math/accent.rs
+++ b/crates/typst-library/src/math/accent.rs
@@ -80,6 +80,19 @@ impl Accent {
pub fn new(c: char) -> Self {
Self(Self::combine(c).unwrap_or(c))
}
+
+ /// List of bottom accents. Currently just a list of ones included in the
+ /// Unicode math class document.
+ const BOTTOM: &[char] = &[
+ '\u{0323}', '\u{032C}', '\u{032D}', '\u{032E}', '\u{032F}', '\u{0330}',
+ '\u{0331}', '\u{0332}', '\u{0333}', '\u{033A}', '\u{20E8}', '\u{20EC}',
+ '\u{20ED}', '\u{20EE}', '\u{20EF}',
+ ];
+
+ /// Whether this accent is a bottom accent or not.
+ pub fn is_bottom(&self) -> bool {
+ Self::BOTTOM.contains(&self.0)
+ }
}
/// This macro generates accent-related functions.
diff --git a/tests/ref/math-accent-bottom-high-base.png b/tests/ref/math-accent-bottom-high-base.png
new file mode 100644
index 00000000..23b14467
--- /dev/null
+++ b/tests/ref/math-accent-bottom-high-base.png
Binary files differ
diff --git a/tests/ref/math-accent-bottom-sized.png b/tests/ref/math-accent-bottom-sized.png
new file mode 100644
index 00000000..5455b2f5
--- /dev/null
+++ b/tests/ref/math-accent-bottom-sized.png
Binary files differ
diff --git a/tests/ref/math-accent-bottom-subscript.png b/tests/ref/math-accent-bottom-subscript.png
new file mode 100644
index 00000000..81854444
--- /dev/null
+++ b/tests/ref/math-accent-bottom-subscript.png
Binary files differ
diff --git a/tests/ref/math-accent-bottom-wide-base.png b/tests/ref/math-accent-bottom-wide-base.png
new file mode 100644
index 00000000..0475b485
--- /dev/null
+++ b/tests/ref/math-accent-bottom-wide-base.png
Binary files differ
diff --git a/tests/ref/math-accent-bottom.png b/tests/ref/math-accent-bottom.png
new file mode 100644
index 00000000..bd1b9214
--- /dev/null
+++ b/tests/ref/math-accent-bottom.png
Binary files differ
diff --git a/tests/ref/math-accent-nested.png b/tests/ref/math-accent-nested.png
new file mode 100644
index 00000000..4b3d58f3
--- /dev/null
+++ b/tests/ref/math-accent-nested.png
Binary files differ
diff --git a/tests/suite/math/accent.typ b/tests/suite/math/accent.typ
index ecc0588c..2239d897 100644
--- a/tests/suite/math/accent.typ
+++ b/tests/suite/math/accent.typ
@@ -58,3 +58,31 @@ $hat(a) hat(A)$
$tilde(w) tilde(W)$
$grave(i) grave(j)$
$grave(I) grave(J)$
+
+--- math-accent-bottom ---
+// Test bottom accents.
+$accent(a, \u{20EE}), accent(T, \u{0323}), accent(xi, \u{0332}),
+ accent(f, \u{20ED}), accent(F, \u{20E8}), accent(y, \u{032E}),
+ accent(!, \u{032F}), accent(J, \u{0333}), accent(p, \u{0331})$
+
+--- math-accent-bottom-wide-base ---
+// Test wide base with bottom accents.
+$accent(x + y, \u{20EF}), accent(sum, \u{032D})$
+
+--- math-accent-bottom-subscript ---
+// Test effect of bottom accent on subscript.
+$q_x != accent(q, \u{032C})_x != accent(accent(q, \u{032C}), \u{032C})_x$
+
+--- math-accent-bottom-high-base ---
+// Test high base with bottom accents.
+$ accent(integral, \u{20EC}), accent(integral, \u{20EC})_a^b, accent(integral_a^b, \u{20EC}) $
+
+--- math-accent-bottom-sized ---
+// Test bottom accent size.
+$accent(sum, \u{0330}), accent(sum, \u{0330}, size: #50%), accent(H, \u{032D}, size: #200%)$
+
+--- math-accent-nested ---
+// Test nested top and bottom accents.
+$hat(accent(L, \u{0330})), accent(circle(p), \u{0323}),
+ macron(accent(caron(accent(A, \u{20ED})), \u{0333})) \
+ breve(accent(eta, \u{032E})) = accent(breve(eta), \u{032E})$