diff --git a/crates/typst-library/src/math/root.rs b/crates/typst-library/src/math/root.rs
index d1c5f46a..0cf5679a 100644
--- a/crates/typst-library/src/math/root.rs
+++ b/crates/typst-library/src/math/root.rs
@@ -46,10 +46,11 @@ impl LayoutMath for RootElem {
/// Layout a root.
///
-/// https://www.w3.org/TR/mathml-core/#radicals-msqrt-mroot
+/// TeXbook page 443, page 360
+/// See also: https://www.w3.org/TR/mathml-core/#radicals-msqrt-mroot
fn layout(
ctx: &mut MathContext,
- mut index: Option<&Content>,
+ index: Option<&Content>,
radicand: &Content,
span: Span,
) -> SourceResult<()> {
@@ -71,25 +72,23 @@ fn layout(
// Layout root symbol.
let target = radicand.height() + thickness + gap;
- let sqrt = precomposed(ctx, index, target)
- .map(|frame| {
- index = None;
- frame
- })
- .unwrap_or_else(|| {
- let glyph = GlyphFragment::new(ctx, '√', span);
- glyph.stretch_vertical(ctx, target, Abs::zero()).frame
- });
+ let sqrt = GlyphFragment::new(ctx, '√', span)
+ .stretch_vertical(ctx, target, Abs::zero())
+ .frame;
// Layout the index.
- // Script-script style looks too small, we use Script style instead.
- ctx.style(ctx.style.with_size(MathSize::Script));
+ ctx.style(ctx.style.with_size(MathSize::ScriptScript));
let index = index.map(|elem| ctx.layout_frame(elem)).transpose()?;
ctx.unstyle();
- let gap = gap.max((sqrt.height() - radicand.height() - thickness) / 2.0);
- let descent = radicand.descent() + gap;
- let inner_ascent = extra_ascender + thickness + gap + radicand.ascent();
+ // TeXbook, page 443, item 11
+ // Keep original gap, and then distribute any remaining free space
+ // equally above and below.
+ let gap = gap.max((sqrt.height() - thickness - radicand.height() + gap) / 2.0);
+
+ let sqrt_ascent = radicand.ascent() + gap + thickness;
+ let descent = sqrt.height() - sqrt_ascent;
+ let inner_ascent = sqrt_ascent + extra_ascender;
let mut sqrt_offset = Abs::zero();
let mut shift_up = Abs::zero();
@@ -97,23 +96,32 @@ fn layout(
if let Some(index) = &index {
sqrt_offset = kern_before + index.width() + kern_after;
- shift_up = raise_factor * sqrt.height() - descent + index.descent();
+ // The formula below for how much raise the index by comes from
+ // the TeXbook, page 360, in the definition of `\root`.
+ // However, the `+ index.descent()` part is different from TeX.
+ // Without it, descenders can collide with the surd, a rarity
+ // in practice, but possible. MS Word also adjusts index positions
+ // for descenders.
+ shift_up = raise_factor * (inner_ascent - descent) + index.descent();
ascent.set_max(shift_up + index.ascent());
}
- let radicant_offset = sqrt_offset + sqrt.width();
- let width = radicant_offset + radicand.width();
+ let radicand_x = sqrt_offset + sqrt.width();
+ let radicand_y = ascent - radicand.ascent();
+ let width = radicand_x + radicand.width();
let size = Size::new(width, ascent + descent);
- let sqrt_pos = Point::new(sqrt_offset, ascent - inner_ascent);
- let line_pos = Point::new(radicant_offset, ascent - inner_ascent + thickness / 2.0);
- let radicand_pos = Point::new(radicant_offset, ascent - radicand.ascent());
+ // The extra "- thickness" comes from the fact that the sqrt is placed
+ // in `push_frame` with respect to its top, not its baseline.
+ let sqrt_pos = Point::new(sqrt_offset, radicand_y - gap - thickness);
+ let line_pos = Point::new(radicand_x, radicand_y - gap - (thickness / 2.0));
+ let radicand_pos = Point::new(radicand_x, radicand_y);
let mut frame = Frame::new(size);
frame.set_baseline(ascent);
if let Some(index) = index {
- let index_pos = Point::new(kern_before, ascent - shift_up - index.ascent());
+ let index_pos = Point::new(kern_before, ascent - index.ascent() - shift_up);
frame.push_frame(index_pos, index);
}
@@ -135,22 +143,3 @@ fn layout(
Ok(())
}
-
-/// Select a precomposed radical, if the font has it.
-fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option {
- let elem = index?.to::()?;
- let c = match elem.text().as_str() {
- "3" => '∛',
- "4" => '∜',
- _ => return None,
- };
-
- ctx.ttf.glyph_index(c)?;
- let glyph = GlyphFragment::new(ctx, c, elem.span());
- let variant = glyph.stretch_vertical(ctx, target, Abs::zero()).frame;
- if variant.height() < target {
- return None;
- }
-
- Some(variant)
-}
diff --git a/tests/ref/bugs/math-realize.png b/tests/ref/bugs/math-realize.png
index 5ce129f6..f5043984 100644
Binary files a/tests/ref/bugs/math-realize.png and b/tests/ref/bugs/math-realize.png differ
diff --git a/tests/ref/math/accent.png b/tests/ref/math/accent.png
index 1065576e..78d5f498 100644
Binary files a/tests/ref/math/accent.png and b/tests/ref/math/accent.png differ
diff --git a/tests/ref/math/attach-p1.png b/tests/ref/math/attach-p1.png
index 0b9b8f34..0c2f2bed 100644
Binary files a/tests/ref/math/attach-p1.png and b/tests/ref/math/attach-p1.png differ
diff --git a/tests/ref/math/attach-p2.png b/tests/ref/math/attach-p2.png
index 242be908..d956059d 100644
Binary files a/tests/ref/math/attach-p2.png and b/tests/ref/math/attach-p2.png differ
diff --git a/tests/ref/math/frac.png b/tests/ref/math/frac.png
index 94990208..0b81e37b 100644
Binary files a/tests/ref/math/frac.png and b/tests/ref/math/frac.png differ
diff --git a/tests/ref/math/numbering.png b/tests/ref/math/numbering.png
index 3952dea0..b8b462ba 100644
Binary files a/tests/ref/math/numbering.png and b/tests/ref/math/numbering.png differ
diff --git a/tests/ref/math/root.png b/tests/ref/math/root.png
index 017582a4..af5c41ea 100644
Binary files a/tests/ref/math/root.png and b/tests/ref/math/root.png differ
diff --git a/tests/ref/math/style.png b/tests/ref/math/style.png
index cf962574..c9bf5d64 100644
Binary files a/tests/ref/math/style.png and b/tests/ref/math/style.png differ