Improved math root layout (#1743)

This commit is contained in:
damaxwell 2023-07-18 05:41:35 -08:00 committed by GitHub
parent 0c94d2b34e
commit 7dc605307c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 31 additions and 42 deletions

View File

@ -46,10 +46,11 @@ impl LayoutMath for RootElem {
/// Layout a root. /// 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( fn layout(
ctx: &mut MathContext, ctx: &mut MathContext,
mut index: Option<&Content>, index: Option<&Content>,
radicand: &Content, radicand: &Content,
span: Span, span: Span,
) -> SourceResult<()> { ) -> SourceResult<()> {
@ -71,25 +72,23 @@ fn layout(
// Layout root symbol. // Layout root symbol.
let target = radicand.height() + thickness + gap; let target = radicand.height() + thickness + gap;
let sqrt = precomposed(ctx, index, target) let sqrt = GlyphFragment::new(ctx, '√', span)
.map(|frame| { .stretch_vertical(ctx, target, Abs::zero())
index = None; .frame;
frame
})
.unwrap_or_else(|| {
let glyph = GlyphFragment::new(ctx, '√', span);
glyph.stretch_vertical(ctx, target, Abs::zero()).frame
});
// Layout the index. // Layout the index.
// Script-script style looks too small, we use Script style instead. ctx.style(ctx.style.with_size(MathSize::ScriptScript));
ctx.style(ctx.style.with_size(MathSize::Script));
let index = index.map(|elem| ctx.layout_frame(elem)).transpose()?; let index = index.map(|elem| ctx.layout_frame(elem)).transpose()?;
ctx.unstyle(); ctx.unstyle();
let gap = gap.max((sqrt.height() - radicand.height() - thickness) / 2.0); // TeXbook, page 443, item 11
let descent = radicand.descent() + gap; // Keep original gap, and then distribute any remaining free space
let inner_ascent = extra_ascender + thickness + gap + radicand.ascent(); // 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 sqrt_offset = Abs::zero();
let mut shift_up = Abs::zero(); let mut shift_up = Abs::zero();
@ -97,23 +96,32 @@ fn layout(
if let Some(index) = &index { if let Some(index) = &index {
sqrt_offset = kern_before + index.width() + kern_after; 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()); ascent.set_max(shift_up + index.ascent());
} }
let radicant_offset = sqrt_offset + sqrt.width(); let radicand_x = sqrt_offset + sqrt.width();
let width = radicant_offset + radicand.width(); let radicand_y = ascent - radicand.ascent();
let width = radicand_x + radicand.width();
let size = Size::new(width, ascent + descent); let size = Size::new(width, ascent + descent);
let sqrt_pos = Point::new(sqrt_offset, ascent - inner_ascent); // The extra "- thickness" comes from the fact that the sqrt is placed
let line_pos = Point::new(radicant_offset, ascent - inner_ascent + thickness / 2.0); // in `push_frame` with respect to its top, not its baseline.
let radicand_pos = Point::new(radicant_offset, ascent - radicand.ascent()); 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); let mut frame = Frame::new(size);
frame.set_baseline(ascent); frame.set_baseline(ascent);
if let Some(index) = index { 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); frame.push_frame(index_pos, index);
} }
@ -135,22 +143,3 @@ fn layout(
Ok(()) Ok(())
} }
/// Select a precomposed radical, if the font has it.
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
let elem = index?.to::<TextElem>()?;
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)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB