diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-10-27 19:04:55 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-27 18:04:55 +0000 |
| commit | be7cfc85d08c545abfac08098b7b33b4bd71f37e (patch) | |
| tree | f4137fa2aaa57babae1f7603a9b2ed7e688f43d8 /crates/typst-layout/src/math/root.rs | |
| parent | b8034a343831e8609aec2ec81eb7eeda57aa5d81 (diff) | |
Split out four new crates (#5302)
Diffstat (limited to 'crates/typst-layout/src/math/root.rs')
| -rw-r--r-- | crates/typst-layout/src/math/root.rs | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/crates/typst-layout/src/math/root.rs b/crates/typst-layout/src/math/root.rs new file mode 100644 index 00000000..0bb2f539 --- /dev/null +++ b/crates/typst-layout/src/math/root.rs @@ -0,0 +1,126 @@ +use typst_library::diag::SourceResult; +use typst_library::foundations::{Packed, StyleChain}; +use typst_library::layout::{Abs, Frame, FrameItem, Point, Size}; +use typst_library::math::{EquationElem, MathSize, RootElem}; +use typst_library::text::TextElem; +use typst_library::visualize::{FixedStroke, Geometry}; + +use super::{style_cramped, FrameFragment, GlyphFragment, MathContext}; + +/// Lays out a [`RootElem`]. +/// +/// TeXbook page 443, page 360 +/// See also: <https://www.w3.org/TR/mathml-core/#radicals-msqrt-mroot> +#[typst_macros::time(name = "math.root", span = elem.span())] +pub fn layout_root( + elem: &Packed<RootElem>, + ctx: &mut MathContext, + styles: StyleChain, +) -> SourceResult<()> { + let index = elem.index(styles); + let radicand = elem.radicand(); + let span = elem.span(); + + let gap = scaled!( + ctx, styles, + text: radical_vertical_gap, + display: radical_display_style_vertical_gap, + ); + let thickness = scaled!(ctx, styles, radical_rule_thickness); + let extra_ascender = scaled!(ctx, styles, radical_extra_ascender); + let kern_before = scaled!(ctx, styles, radical_kern_before_degree); + let kern_after = scaled!(ctx, styles, radical_kern_after_degree); + let raise_factor = percent!(ctx, radical_degree_bottom_raise_percent); + + // Layout radicand. + let radicand = { + let cramped = style_cramped(); + let styles = styles.chain(&cramped); + let run = ctx.layout_into_run(radicand, styles)?; + let multiline = run.is_multiline(); + let mut radicand = run.into_fragment(ctx, styles).into_frame(); + if multiline { + // Align the frame center line with the math axis. + radicand.set_baseline( + radicand.height() / 2.0 + scaled!(ctx, styles, axis_height), + ); + } + radicand + }; + + // Layout root symbol. + let target = radicand.height() + thickness + gap; + let sqrt = GlyphFragment::new(ctx, styles, '√', span) + .stretch_vertical(ctx, target, Abs::zero()) + .frame; + + // Layout the index. + let sscript = EquationElem::set_size(MathSize::ScriptScript).wrap(); + let index = index + .as_ref() + .map(|elem| ctx.layout_into_frame(elem, styles.chain(&sscript))) + .transpose()?; + + // 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(); + let mut ascent = inner_ascent; + + if let Some(index) = &index { + sqrt_offset = kern_before + index.width() + kern_after; + // 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 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); + + // 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::soft(size); + frame.set_baseline(ascent); + + if let Some(index) = index { + let index_pos = Point::new(kern_before, ascent - index.ascent() - shift_up); + frame.push_frame(index_pos, index); + } + + frame.push_frame(sqrt_pos, sqrt); + frame.push( + line_pos, + FrameItem::Shape( + Geometry::Line(Point::with_x(radicand.width())).stroked( + FixedStroke::from_pair( + TextElem::fill_in(styles).as_decoration(), + thickness, + ), + ), + span, + ), + ); + + frame.push_frame(radicand_pos, radicand); + ctx.push(FrameFragment::new(ctx, styles, frame)); + + Ok(()) +} |
