How to create the colimit symbol \varinjlim?

I want to create the colimit symbol $\varinjlim$ in Typst. Here is what I have so far:

#let rightarrow = $stretch(->, size: #15pt)$

#let movebase(size, x) = text(baseline: size)[#x]

#let injlim = $display(limits(lim_(movebase(#(-1.9pt),rightarrow))))$

#let varinjlim(subscript) = $injlim_movebase(#(-2.8pt), subscript)$

$
  limits(lim_(rightarrow))_(i in I) F(U_i) = injlim_(i in I) F(U_i) = varinjlim(i in I) F(U_i)
$

The default limits(lim_(rightarrow))_(i in I) (displayed as the leftmost formula in the equation above) does not produce the desired vertical spacing. To fix this, I defined a function movebase to adjust the baseline. Using this function, varinjlim(i in I) (the rightmost formula) achieves the correct vertical spacing.

However, injlim_(i in I) (the middle formula) still fails to produce the desired vertical spacing. To correctly position the subscript, I had to define an additional function: #let varinjlim(subscript) = $injlim_movebase(#(-2.8pt), subscript)$. While this workaround is effective, it is far less convenient compared to LaTeX’s $\varinjlim$.

Is there a way to define injlim such that injlim_(i in I) F(U_i) automatically produces the correct vertical spacing without requiring the extra function?

Maybe something like the following?

#let rightarrow = pad(top: -0.6em, math.stretch(math.arrow, size: 1.9em))
$
limits(lim_(rightarrow))_(i in I)
$
![image|90x108](upload://vPXwuvG92cqzpUsbgxqZO0UP3uP.png)

(the image copy-paste doesn't seem to work for some reason...)
1 Like

Thanks! pad works perfectly here and simplifies things significantly. As I have shown in the above example, I also want precise control over the vertical spacing between the arrow and the subscript i in I. Following your suggestion, I found that adding padding to the bottom as shown below

#let myrightarrow = pad(top: -0.6em, math.stretch(math.arrow, size: 1.9em))
#let padlim = $op(#pad(bottom: 0.43em, $limits(lim)_myrightarrow$))$

can shift the whole limit upward.

To restore the correct baseline, I can then use

#let movebaselim = movebase(0.085em, padlim)

This approach lets me achieve the desired vertical spacing. As a result, movebaselim can replicate the vertical spacing of the varinjlim I defined earlier, without relying on any additional functions. Here’s what I have:

$
    limits(lim_(myrightarrow))_(i in I) F(U_i)
  = limits(padlim)_(i in I) F(U_i)
  = limits(movebaselim)_(i in I) F(U_i)
  = varinjlim(i in I) F(U_i)
$

The third formula visually matches the fourth one in the above image.

However, ideally, I would like to use a single parameter to control the vertical spacing between the arrow and the subscript. My current approach relies on trial and error to determine the values for #pad(bottom: 0.43em, ..) and movebase(0.085em, padlim). While the adjusted baseline appears almost visually accurate, it lacks precision and requires a cumbersome process of fine-tuning.

My question is: how can I use only one parameter to control the vertical spacing between the arrow and the subscript?

I found a solution that matches your requirements using a set rule with text(baseline: shift).

#let lim-width() = measure[lim].width

#let baseline-subscript(shift, subscript) = context {
  set text(baseline: shift)
  math.stretch(math.arrow, size: lim-width())
  linebreak()
  set text(baseline: 2 * shift)
  subscript
}

$ limits(lim_#baseline-subscript(0.0em, $i in I$)) $

You can find the project to generate the examples in the image here and I also included an explanation of the different examples below.

If you use the subscript with the function limits(), the spacing between lim, the arrow and the actual subscript will already be correct by default as long as you include everything in a single subscript separated by a linebreak(). This is the “default” example in the image above. I know that the arrow does not have the correct width, it is just supposed to show how the vertical spacing in the subscript works. See the function simple-subscript() to also get the correct width for the arrow.

If you wrap the arrow in the function pad(), it will be “promoted” from a low-case height to the upper-case height. This already messes up the spacing even if you use pad(top: 0.0em). I tried to illustrate this by wrapping the arrow and the subscript in red boxes. Both pad() and box() seem to use the "cap-height" as the top-edge regardless of the content. While you can get the correct box for the arrow with the rule set text(top-edge: "bounds"), I couldn’t get the right spacing between lim, the arrow and the subscript with the function pad() and a varying shift.

If you use the function text(baseline: shift), you can apply a shift to the arrow and the subscript without messing up the height of the arrow. The baseline of the subscript is shifted by 2 * shift since it is not aware of the shifted arrow.

1 Like

Thank you! Your answer clarifies a lot.

#let lim-width() = measure[lim].width

#let baseline-subscript(shift, subscript) = context {
  set text(baseline: shift)
  math.stretch(math.arrow, size: lim-width())
  linebreak()
  set text(baseline: 2 * shift)
  subscript
}

This is a clean implementation and produces aesthetically pleasing results.

I was wondering if there’s a way to define a new symbol, injlim, that behaves like the standard lim but allows me to customize its vertical spacing for the subscript.

#let movebase(size, x) = text(baseline: size)[#x]
#let myrightarrow = pad(top: -0.6em, math.stretch(math.arrow, size: 1.9em))
#let padlim = $op(#pad(bottom: 0.43em, $limits(lim)_myrightarrow$))$
#let injlim = movebase(0.085em, padlim)

$injlim_(i in I) F(U_i)$ $ limits(injlim)_(i in I) F(U_i) $

My current attempt more or less achieves something close, but it feels a bit hacky. I haven’t tested it thoroughly, so it may cause issues elsewhere.

Now I’m aware that this might not be an easy task in Typst, but I’m still curious if there’s a more optimal way to define new math symbols like injlim.

I’m not sure that I know what you would still like to optimize? Do you want the implementation to be even shorter? In that case you could create another function that directly includes limits() etc…

#let varinjlim(body) = $ limits(lim_#baseline-subscript(body)) $

$ #varinjlim[$i in I$] $

If you want to set the baseline shift for the entire document, you can use a state variable that is used by default unless you specify a certain shift.