Subscript/superscript vertical placement after delimiters

I noticed that subscripts and superscripts are not placed correctly when the size of a delimiter is changed (made taller/bigger for example). For example if you manually adjust the height of ) or ] the subscript is no longer vertically aligned correctly. I wrote a short code to fix this issue. Hopefully this can be handled automatically by Typst in future versions.

I cannot upload the file so I am pasting the content below.

// math-fences.typ

//

// Delimiters whose height you choose, with subscripts and superscripts placed

// against the closing glyph instead of dangling below a tall pair of bars.

//

//

// Why this exists

//

// A script in Typst binds to the atom before it, and the built-in `norm`, `abs`

// and auto-scaled `( )` grow to fit whatever they wrap. Together those two facts

// mean `norm(sum_(i,j) a_(i j))_(S_1)` puts the subscript at the bottom edge of

// full-height bars, well below the baseline. Shrinking the bars with

// `lr(..., size: ...)` does not fix it, because the script still attaches to the

// whole group.

//

// The delimiters here tag themselves with `metadata`. A `math.attach` show rule

// spots the tag and rebuilds the expression, attaching the script to the closing

// glyph at a height you control. You keep ordinary `_` and `^` syntax.

//

//

// Usage

//

//   #import "math-fences.typ": fences, norm, absv, parn, brak, brac

//   #show: fences                        // installs the show rule

//

//   $ norm(sum_(i,j) a_(i j) e_i e_j^T)_(S_1) $

//   $ norm(x, h: #1.5em)_(S_1)^* $

//   $ parn(sum_j sigma_j (A)^p)^(1/p) $

//

// If a template already applies the rule (see `show math.attach:` below), you do

// not need `#show: fences` as well.

//

//

// The h argument

//

// A length gives a fixed height, so every delimiter you pass the same value

// comes out the same size. A ratio scales each delimiter to its own content.

// Ratios below 100% are the point: the delimiter has to be shorter than what it

// wraps for a subscript to sit near the baseline. At 100% you are back to the

// built-in behaviour.

//

//

// Gotchas

//

//   - Called from math mode, value arguments need a #: `h: #1.5em`, not `1.5em`.

//   - Importing `norm` shadows the built-in `math.norm` for the whole file.

//   - Without the show rule active these still draw bars, but scripts revert to

//     the built-in placement.

//   - Any glyph with vertical variants in the math font can be stretched, so

//     `#let ceil = fenced.with(sym.ceil.l, sym.ceil.r)` works.

#let fence-bars(l, r, body, h: 80%, sub: none, sup: none) = context {

let hh = if type(h) == ratio {

h * measure(math.equation(block: true, body)).height

} else { h }

(

math.stretch(l, size: hh)

+ body

+ math.attach(math.stretch(r, size: hh), br: sub, tr: sup)

)

}

// The bars are drawn here as well as in the show rule, so a fence with no

// script attached still renders something.

#let fenced(l, r, body, h: 80%) = {

(

metadata((fenced: true, l: l, r: r, h: h, body: body))

+ fence-bars(l, r, body, h: h)

)

}

#let norm = fenced.with(sym.bar.v.double, sym.bar.v.double)

#let absv = fenced.with(sym.bar.v, sym.bar.v)

#let parn = fenced.with(sym.paren.l, sym.paren.r)

#let brak = fenced.with(sym.bracket.l, sym.bracket.r)

#let brac = fenced.with(sym.brace.l, sym.brace.r)

// Rebuild a tagged fence with the script on the closing glyph. Note that `x_S`

// populates the `b` field rather than `br`, and `x^2` populates `t` rather than

// `tr`; reading only `br`/`tr` drops the script silently.

#let fence-attach-rule(it) = {

let base = it.base

let first = if base.has("children") { base.children.at(0, default: none) } else { none }

let tagged = (

first != none

and first.func() == metadata

and type(first.value) == dictionary

and first.value.at("fenced", default: false)

)

if not tagged { return it }

let m = first.value

let sub = if it.at("b", default: none) != none { it.b } else { it.at("br", default: none) }

let sup = if it.at("t", default: none) != none { it.t } else { it.at("tr", default: none) }

fence-bars(m.l, m.r, m.body, h: m.h, sub: sub, sup: sup)

}

// Apply with `#show: fences`.

#let fences(body) = {

show math.attach: fence-attach-rule

body

}