diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index 7e7eeea5f..ff01bacfd 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -393,11 +393,6 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { continue; } - // Separate primes and superscripts to different attachments. - if primed && p.current() == SyntaxKind::Hat { - p.wrap(m, SyntaxKind::MathAttach); - } - let Some((kind, stop, assoc, mut prec)) = math_op(p.current()) else { // No attachments, so we need to wrap primes as attachment. if primed { @@ -429,7 +424,7 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { math_expr_prec(p, prec, stop); math_unparen(p, m2); - if p.eat_if(SyntaxKind::Underscore) || (!primed && p.eat_if(SyntaxKind::Hat)) { + if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) { let m3 = p.marker(); math_expr_prec(p, prec, SyntaxKind::End); math_unparen(p, m3); diff --git a/crates/typst/src/eval/math.rs b/crates/typst/src/eval/math.rs index 0e5e0eef0..548c935dc 100644 --- a/crates/typst/src/eval/math.rs +++ b/crates/typst/src/eval/math.rs @@ -54,7 +54,11 @@ impl Eval for ast::MathAttach<'_> { if let Some(expr) = self.top() { elem.push_t(Some(expr.eval_display(vm)?)); - } else if let Some(primes) = self.primes() { + } + + // Always attach primes in scripts style (not limits style), + // i.e. at the top-right corner. + if let Some(primes) = self.primes() { elem.push_tr(Some(primes.eval(vm)?)); } diff --git a/crates/typst/src/math/attach.rs b/crates/typst/src/math/attach.rs index 7cc03bbab..8d85f8c41 100644 --- a/crates/typst/src/math/attach.rs +++ b/crates/typst/src/math/attach.rs @@ -52,31 +52,47 @@ pub struct AttachElem { impl LayoutMath for Packed { #[typst_macros::time(name = "math.attach", span = self.span())] fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> { - type GetAttachment = fn(&AttachElem, styles: StyleChain) -> Option; - - let layout_attachment = - |ctx: &mut MathContext, styles: StyleChain, getter: GetAttachment| { - getter(self, styles) - .map(|elem| ctx.layout_into_fragment(&elem, styles)) - .transpose() - }; - let base = ctx.layout_into_fragment(self.base(), styles)?; let sup_style = style_for_superscript(styles); - let tl = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::tl)?; - let tr = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::tr)?; - let t = layout_attachment(ctx, styles.chain(&sup_style), AttachElem::t)?; + let sup_style_chain = styles.chain(&sup_style); + let tl = self.tl(sup_style_chain); + let tr = self.tr(sup_style_chain); + let primed = tr.as_ref().is_some_and(|content| content.is::()); + let t = self.t(sup_style_chain); let sub_style = style_for_subscript(styles); - let bl = layout_attachment(ctx, styles.chain(&sub_style), AttachElem::bl)?; - let br = layout_attachment(ctx, styles.chain(&sub_style), AttachElem::br)?; - let b = layout_attachment(ctx, styles.chain(&sub_style), AttachElem::b)?; + let sub_style_chain = styles.chain(&sub_style); + let bl = self.bl(sub_style_chain); + let br = self.br(sub_style_chain); + let b = self.b(sub_style_chain); let limits = base.limits().active(styles); - let (t, tr) = if limits || tr.is_some() { (t, tr) } else { (None, t) }; + let (t, tr) = match (t, tr) { + (Some(t), Some(tr)) if primed && !limits => (None, Some(tr + t)), + (Some(t), None) if !limits => (None, Some(t)), + (t, tr) => (t, tr), + }; let (b, br) = if limits || br.is_some() { (b, br) } else { (None, b) }; - layout_attachments(ctx, styles, base, [tl, t, tr, bl, b, br]) + + macro_rules! layout { + ($content:ident, $style_chain:ident) => { + $content + .map(|elem| ctx.layout_into_fragment(&elem, $style_chain)) + .transpose() + }; + } + + let fragments = [ + layout!(tl, sup_style_chain)?, + layout!(t, sup_style_chain)?, + layout!(tr, sup_style_chain)?, + layout!(bl, sub_style_chain)?, + layout!(b, sub_style_chain)?, + layout!(br, sub_style_chain)?, + ]; + + layout_attachments(ctx, styles, base, fragments) } } diff --git a/tests/ref/math-primes-complex.png b/tests/ref/math-primes-complex.png index 5f5558eb2..0e85d08d3 100644 Binary files a/tests/ref/math-primes-complex.png and b/tests/ref/math-primes-complex.png differ diff --git a/tests/ref/math-primes-with-superscript.png b/tests/ref/math-primes-with-superscript.png new file mode 100644 index 000000000..88a892b97 Binary files /dev/null and b/tests/ref/math-primes-with-superscript.png differ diff --git a/tests/suite/math/primes.typ b/tests/suite/math/primes.typ index e10f8876c..78bb915aa 100644 --- a/tests/suite/math/primes.typ +++ b/tests/suite/math/primes.typ @@ -12,7 +12,11 @@ $a'_b^c$, $a_b'^c$, $a_b^c'$, $a_b'^c'^d'$ $(a'_b')^(c'_d')$, $a'/b'$, $a_b'/c_d'$ -$∫'$, $∑'$, $ ∑'_S' $ +$∫'$, $∑'$, $a'^2^2$, $a'_2_2$ + +$f_n'^a'$, $f^a'_n'$ + +$ ∑'_S' $ --- math-primes-attach --- // Test attaching primes only @@ -48,3 +52,12 @@ $ #g''''''''''''''''' \ gg' $ + +--- math-primes-with-superscript --- +// Test prime symbols don't raise the superscript position +$ + sqrt(f)/f + sqrt(f^2)/f^2 + sqrt(f'^2)/f'^2 + sqrt(f''_n^2)/f''^2_n +$