diff --git a/library/src/math/attach.rs b/library/src/math/attach.rs index cbf083c0e..eee1e80ec 100644 --- a/library/src/math/attach.rs +++ b/library/src/math/attach.rs @@ -77,18 +77,9 @@ impl LayoutMath for AttachElem { let b = layout_attachment(ctx, Self::b)?; ctx.unstyle(); - let as_limits = self.base().is::() - || (!self.base().is::() - && 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, - }); - - let (t, tr) = if as_limits || tr.is_some() { (t, tr) } else { (None, t) }; - let (b, br) = if as_limits || br.is_some() { (b, br) } else { (None, b) }; + let limits = base.limits().active(ctx); + let (t, tr) = if limits || tr.is_some() { (t, tr) } else { (None, t) }; + let (b, br) = if limits || br.is_some() { (b, br) } else { (None, b) }; layout_attachments(ctx, base, [tl, t, tr, bl, b, br]) } } @@ -112,7 +103,10 @@ pub struct ScriptsElem { impl LayoutMath for ScriptsElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - self.body().layout_math(ctx) + let mut fragment = ctx.layout_fragment(&self.body())?; + fragment.set_limits(Limits::Never); + ctx.push(fragment); + Ok(()) } } @@ -135,10 +129,56 @@ pub struct LimitsElem { impl LayoutMath for LimitsElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - self.body().layout_math(ctx) + let mut fragment = ctx.layout_fragment(&self.body())?; + fragment.set_limits(Limits::Always); + ctx.push(fragment); + Ok(()) } } +/// Describes in which situation a frame should use limits for attachments. +#[derive(Debug, Copy, Clone)] +pub enum Limits { + /// Always scripts. + Never, + /// Display limits only in `display` math. + Display, + /// Always limits. + Always, +} + +impl Limits { + /// The default limit configuration if the given character is the base. + pub fn for_char(c: char) -> Self { + if Self::DEFAULT_TO_LIMITS.contains(&c) { + Limits::Display + } else { + Limits::Never + } + } + + /// Whether limits should be displayed in this context + pub fn active(&self, ctx: &MathContext) -> bool { + match self { + Self::Always => true, + Self::Display => ctx.style.size == MathSize::Display, + Self::Never => false, + } + } + + /// Unicode codepoints that should show attachments as limits in display + /// mode. + #[rustfmt::skip] + const DEFAULT_TO_LIMITS: &[char] = &[ + /* ∏ */ '\u{220F}', /* ∐ */ '\u{2210}', /* ∑ */ '\u{2211}', + /* ⋀ */ '\u{22C0}', /* ⋁ */ '\u{22C1}', + /* ⋂ */ '\u{22C2}', /* ⋃ */ '\u{22C3}', + /* ⨀ */ '\u{2A00}', /* ⨁ */ '\u{2A01}', /* ⨂ */ '\u{2A02}', + /* ⨃ */ '\u{2A03}', /* ⨄ */ '\u{2A04}', + /* ⨅ */ '\u{2A05}', /* ⨆ */ '\u{2A06}', + ]; +} + macro_rules! measure { ($e: ident, $attr: ident) => { $e.as_ref().map(|e| e.$attr()).unwrap_or_default() @@ -358,14 +398,3 @@ fn is_atomic_text_frame(frame: &Frame) -> bool { .filter(|item| !matches!(item, FrameItem::Meta(_, _))); matches!(iter.next(), Some(FrameItem::Text(_))) && iter.next().is_none() } - -/// Unicode codepoints that should have sub- and superscripts attached as limits. -#[rustfmt::skip] -const LIMITS: &[char] = &[ - /* ∏ */ '\u{220F}', /* ∐ */ '\u{2210}', /* ∑ */ '\u{2211}', - /* ⋀ */ '\u{22C0}', /* ⋁ */ '\u{22C1}', - /* ⋂ */ '\u{22C2}', /* ⋃ */ '\u{22C3}', - /* ⨀ */ '\u{2A00}', /* ⨁ */ '\u{2A01}', /* ⨂ */ '\u{2A02}', - /* ⨃ */ '\u{2A03}', /* ⨄ */ '\u{2A04}', - /* ⨅ */ '\u{2A05}', /* ⨆ */ '\u{2A06}', -]; diff --git a/library/src/math/fragment.rs b/library/src/math/fragment.rs index 027ab7da1..b70a3f7dc 100644 --- a/library/src/math/fragment.rs +++ b/library/src/math/fragment.rs @@ -90,6 +90,15 @@ impl MathFragment { } } + pub fn set_limits(&mut self, limits: Limits) { + match self { + Self::Glyph(glyph) => glyph.limits = limits, + Self::Variant(variant) => variant.limits = limits, + Self::Frame(fragment) => fragment.limits = limits, + _ => {} + } + } + pub fn is_spaced(&self) -> bool { match self { MathFragment::Frame(frame) => frame.spaced, @@ -113,6 +122,15 @@ impl MathFragment { _ => Frame::new(self.size()), } } + + pub fn limits(&self) -> Limits { + match self { + MathFragment::Glyph(glyph) => glyph.limits, + MathFragment::Variant(variant) => variant.limits, + MathFragment::Frame(fragment) => fragment.limits, + _ => Limits::Never, + } + } } impl From for MathFragment { @@ -149,6 +167,7 @@ pub struct GlyphFragment { pub class: Option, pub span: Span, pub meta: Vec, + pub limits: Limits, } impl GlyphFragment { @@ -164,6 +183,10 @@ impl GlyphFragment { } pub fn with_id(ctx: &MathContext, c: char, id: GlyphId, span: Span) -> Self { + let class = match c { + ':' => Some(MathClass::Relation), + _ => unicode_math_class::class(c), + }; let mut fragment = Self { id, c, @@ -175,11 +198,9 @@ impl GlyphFragment { width: Abs::zero(), ascent: Abs::zero(), descent: Abs::zero(), + limits: Limits::for_char(c), italics_correction: Abs::zero(), - class: match c { - ':' => Some(MathClass::Relation), - _ => unicode_math_class::class(c), - }, + class, span, meta: MetaElem::data_in(ctx.styles()), }; @@ -224,6 +245,7 @@ impl GlyphFragment { italics_correction: self.italics_correction, class: self.class, span: self.span, + limits: self.limits, frame: self.into_frame(), } } @@ -268,6 +290,7 @@ pub struct VariantFragment { pub font_size: Abs, pub class: Option, pub span: Span, + pub limits: Limits, } impl Debug for VariantFragment { @@ -282,7 +305,7 @@ pub struct FrameFragment { pub style: MathStyle, pub font_size: Abs, pub class: MathClass, - pub limits: bool, + pub limits: Limits, pub spaced: bool, pub base_ascent: Abs, } @@ -296,7 +319,7 @@ impl FrameFragment { font_size: ctx.size, style: ctx.style, class: MathClass::Normal, - limits: false, + limits: Limits::Never, spaced: false, base_ascent, } @@ -306,7 +329,7 @@ impl FrameFragment { Self { class, ..self } } - pub fn with_limits(self, limits: bool) -> Self { + pub fn with_limits(self, limits: Limits) -> Self { Self { limits, ..self } } diff --git a/library/src/math/op.rs b/library/src/math/op.rs index cc57eb614..8ad74c49e 100644 --- a/library/src/math/op.rs +++ b/library/src/math/op.rs @@ -26,7 +26,7 @@ pub struct OpElem { #[required] pub text: EcoString, - /// Whether the operator should force attachments to display as limits. + /// Whether the operator should show attachments as limits in display mode. #[default(false)] pub limits: bool, } @@ -39,7 +39,11 @@ impl LayoutMath for OpElem { ctx.push( FrameFragment::new(ctx, fragment.into_frame()) .with_class(MathClass::Large) - .with_limits(self.limits(ctx.styles())), + .with_limits(if self.limits(ctx.styles()) { + Limits::Display + } else { + Limits::Never + }), ); Ok(()) } diff --git a/library/src/math/stretch.rs b/library/src/math/stretch.rs index bbb0c9c44..910f7a818 100644 --- a/library/src/math/stretch.rs +++ b/library/src/math/stretch.rs @@ -185,6 +185,7 @@ fn assemble( italics_correction: Abs::zero(), class: base.class, span: base.span, + limits: base.limits, } } diff --git a/tests/ref/math/attach.png b/tests/ref/math/attach.png index ab93f25bf..bf63cccec 100644 Binary files a/tests/ref/math/attach.png and b/tests/ref/math/attach.png differ diff --git a/tests/typ/math/attach.typ b/tests/typ/math/attach.typ index f5f49627a..cb90e5a52 100644 --- a/tests/typ/math/attach.typ +++ b/tests/typ/math/attach.typ @@ -91,3 +91,11 @@ $ limits(integral)_a^b != integral_a^b $ --- // Error: 30-34 unknown variable: oops $ attach(A, t: #locate(it => oops)) $ + +--- +// Show and let rules for limits and scripts +#let eq = $ ∫_a^b iota_a^b $ +#eq +#show "∫": math.limits +#show math.iota: math.limits +#eq