From cfca1158045be4a3bfeaac5b12ac2aac182fa77c Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 22 Jan 2023 13:28:46 +0100 Subject: [PATCH] Math sub-, superscript and limit layout --- library/src/math/script.rs | 146 +++++++++++++++++++++++++++++++++---- library/src/text/shift.rs | 6 -- 2 files changed, 130 insertions(+), 22 deletions(-) diff --git a/library/src/math/script.rs b/library/src/math/script.rs index c0255a2ee..671651d9c 100644 --- a/library/src/math/script.rs +++ b/library/src/math/script.rs @@ -3,9 +3,6 @@ use super::*; /// # Script /// A mathematical sub- and/or superscript. /// -/// _Note:_ In the future, this might be unified with the [sub](@sub) and -/// [super](@super) functions that handle sub- and superscripts in text. -/// /// ## Syntax /// This function also has dedicated syntax: Use the underscore (`_`) to /// indicate a subscript and the circumflex (`^`) to indicate a superscript. @@ -28,7 +25,7 @@ use super::*; /// ## Category /// math #[func] -#[capable(Texify)] +#[capable(LayoutMath)] #[derive(Debug, Hash)] pub struct ScriptNode { /// The base. @@ -49,22 +46,139 @@ impl ScriptNode { } } -impl Texify for ScriptNode { - fn texify(&self, t: &mut Texifier) -> SourceResult<()> { - self.base.texify(t)?; +impl LayoutMath for ScriptNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + let base = ctx.layout_fragment(&self.base)?; - if let Some(sub) = &self.sub { - t.push_str("_{"); - sub.texify_unparen(t)?; - t.push_str("}"); + let mut sub = Frame::new(Size::zero()); + if let Some(node) = &self.sub { + ctx.style(ctx.style.for_subscript()); + sub = ctx.layout_frame(node)?; + ctx.unstyle(); } - if let Some(sup) = &self.sup { - t.push_str("^{"); - sup.texify_unparen(t)?; - t.push_str("}"); + let mut sup = Frame::new(Size::zero()); + if let Some(node) = &self.sup { + ctx.style(ctx.style.for_superscript()); + sup = ctx.layout_frame(node)?; + ctx.unstyle(); } - Ok(()) + let render_limits = ctx.style.size == MathSize::Display + && base.class() == Some(MathClass::Large) + && match &base { + MathFragment::Variant(variant) => LIMITS.contains(&variant.c), + MathFragment::Frame(fragment) => fragment.limits, + _ => false, + }; + + if render_limits { + limits(ctx, base, sub, sup) + } else { + scripts(ctx, base, sub, sup, self.sub.is_some() && self.sup.is_some()) + } } } + +/// Layout normal sub- and superscripts. +fn scripts( + ctx: &mut MathContext, + base: MathFragment, + sub: Frame, + sup: Frame, + both: bool, +) -> SourceResult<()> { + let sup_shift_up = if ctx.style.cramped { + scaled!(ctx, superscript_shift_up_cramped) + } else { + scaled!(ctx, superscript_shift_up) + }; + let sup_bottom_min = scaled!(ctx, superscript_bottom_min); + let sup_bottom_max_with_sub = scaled!(ctx, superscript_bottom_max_with_subscript); + let sup_drop_max = scaled!(ctx, superscript_baseline_drop_max); + let gap_min = scaled!(ctx, sub_superscript_gap_min); + let sub_shift_down = scaled!(ctx, subscript_shift_down); + let sub_top_max = scaled!(ctx, subscript_top_max); + let sub_drop_min = scaled!(ctx, subscript_baseline_drop_min); + let space_after = scaled!(ctx, space_after_script); + + let mut shift_up = sup_shift_up + .max(base.ascent() - sup_drop_max) + .max(sup_bottom_min + sup.descent()); + + let mut shift_down = sub_shift_down + .max(base.descent() + sub_drop_min) + .max(sub.ascent() - sub_top_max); + + if both { + let sup_bottom = shift_up - sup.descent(); + let sub_top = sub.ascent() - shift_down; + let gap = sup_bottom - sub_top; + if gap < gap_min { + let increase = gap_min - gap; + let sup_only = + (sup_bottom_max_with_sub - sup_bottom).clamp(Abs::zero(), increase); + let rest = (increase - sup_only) / 2.0; + shift_up += sup_only + rest; + shift_down += rest; + } + } + + let delta = base.italics_correction(); + let ascent = shift_up + sup.ascent(); + let descent = shift_down + sub.descent(); + let height = ascent + descent; + let width = base.width() + sup.width().max(sub.width() - delta) + space_after; + let base_pos = Point::with_y(ascent - base.ascent()); + let sup_pos = Point::with_x(base.width()); + let sub_pos = Point::new(base.width() - delta, height - sub.height()); + let class = base.class().unwrap_or(MathClass::Normal); + + let mut frame = Frame::new(Size::new(width, height)); + frame.set_baseline(ascent); + frame.push_frame(base_pos, base.to_frame(ctx)); + frame.push_frame(sub_pos, sub); + frame.push_frame(sup_pos, sup); + ctx.push(FrameFragment { frame, class, limits: false }); + + Ok(()) +} + +/// Layout limits. +fn limits( + ctx: &mut MathContext, + base: MathFragment, + sub: Frame, + sup: Frame, +) -> SourceResult<()> { + let upper_gap_min = scaled!(ctx, upper_limit_gap_min); + let upper_rise_min = scaled!(ctx, upper_limit_baseline_rise_min); + let lower_gap_min = scaled!(ctx, lower_limit_gap_min); + let lower_drop_min = scaled!(ctx, lower_limit_baseline_drop_min); + + let sup_gap = upper_gap_min.max(upper_rise_min - sup.descent()); + let sub_gap = lower_gap_min.max(lower_drop_min - sub.ascent()); + + let delta = base.italics_correction() / 2.0; + let width = base.width().max(sup.width()).max(sub.width()); + let height = sup.height() + sup_gap + base.height() + sub_gap + sub.height(); + let base_pos = Point::new((width - base.width()) / 2.0, sup.height() + sup_gap); + let sup_pos = Point::with_x((width - sup.width()) / 2.0 + delta); + let sub_pos = Point::new((width - sub.width()) / 2.0 - delta, height - sub.height()); + let class = base.class().unwrap_or(MathClass::Normal); + + let mut frame = Frame::new(Size::new(width, height)); + frame.set_baseline(base_pos.y + base.ascent()); + frame.push_frame(base_pos, base.to_frame(ctx)); + frame.push_frame(sub_pos, sub); + frame.push_frame(sup_pos, sup); + ctx.push(FrameFragment { frame, class, limits: false }); + + Ok(()) +} + +/// Codepoints that should have sub- and superscripts attached as limits. +const LIMITS: &[char] = &[ + '\u{2210}', '\u{22C1}', '\u{22C0}', '\u{2A04}', '\u{22C2}', '\u{22C3}', '\u{220F}', + '\u{2211}', '\u{2A02}', '\u{2A01}', '\u{2A00}', '\u{2A06}', +]; diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index 611ed4557..81b3c115a 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -9,9 +9,6 @@ use crate::prelude::*; /// /// The text is rendered smaller and its baseline is lowered. /// -/// _Note:_ In the future, this might be unified with the [script](@script) -/// function that handles subscripts in math. -/// /// ## Example /// ``` /// Revenue#sub[yearly] @@ -93,9 +90,6 @@ impl Show for SubNode { /// /// The text is rendered smaller and its baseline is raised. /// -/// _Note:_ In the future, this might be unified with the [script](@script) -/// function that handles superscripts in math. -/// /// ## Example /// ``` /// 1#super[st] try!