How can I make my custom math mode functions respond to spaces like built-in math mode functions?

I want to make a custom function for math mode, but how it responds to spaces in the surrounding text is different that that of built-in math mode functions. For example, to get myhat to have the layout I want, I have to use harder-to-read, verbose syntax:

#set page(width: 10cm)

// Puts a special hat on top of the body 
#let myhat(body) = {
  set align(center)
  set par(spacing: 0pt)

  box(stack(
    spacing: 1pt,
    dir: btt, 
    {
      set text(top-edge: "bounds")
      $body$
    },
    {
      set text(top-edge: "bounds", bottom-edge: "bounds")
      math.ast.triple
    }
  ))
}

// Behavior of built-in math functions is good, behavior of custom functions means there are extra spaces
$ 
"Built-in hat:"quad & 2 y hat(x) e^j \

"Custom hat:"quad & 2 y myhat(x) e^j \

"Bothersome workaround:"quad & 2 y#myhat($x$)e^j
$

I assume this is related to this GitHub issue.

Sorry to ask, but are you not looking for Accent Function – Typst Documentation? Or are you trying to do something different?

I just tried that, and unfortunately that doesn’t solve the problem. if you naively put ast.triple as an accent, it overlaps with the base. But you can’t move() it or something, since accent() takes a symbol as the parameter, not content.

1 Like

Yes, I thought the same thing since the accent function documentation says that the accent parameter of the accent function can be content, but it doesn’t seem to work right now as SillyFreak mentioned. Besides, my question is primarily for the more general case of creating an arbitrary math mode function that does placing, creates boxes, or anything like that.

In a reply in the old GitHub issue I linked, Enivex said that there “will be a clearer separation between variables and text in math”:

For now you will have to write $sum_(psi in"abc")$ if you do not want the space there. (This will presumably improve once there is a clearer separation between variables and text in math)

I suppose based on what I’m seeing that boxes are treated like text, not variables. In my case, I guess I want a way to have them be treated like variables?

1 Like

The reason for it being space sensitive is basically because it is laid out “externally”.

On the development version show rules are now always applied in math, so you can now make use of this lovely hack:

#show "\u{0302}": box(inset: (bottom: 5pt), text(0.5em, sym.diamond.small))
$hat(X)$, $hat(x)$

So, you can do something like this to create your myhat function.

#let myhat(body) = {
  // the values here probably need some adjusting
  show "\u{0302}": box(inset: (bottom: 5pt), text(0.5em, sym.last.triple))
  math.hat(body)
}
$ 2y hat(x) e^j quad 2y myhat(x) e^j $
1 Like

By adding a weak spacing

h(0pt, weak: true)

at the beginning and end of your myhat function, it collapses the spaces around x.

// Puts a special hat on top of the body 
#let myhat(body) = {
  h(0pt, weak: true)
  set align(center)
  set par(spacing: 0pt)

  box(stack(
    spacing: 1pt,
    dir: btt, 
    {
      set text(top-edge: "bounds")
      $body$
    },
    {
      set text(top-edge: "bounds", bottom-edge: "bounds")
      math.ast.triple
    }
  ))
  h(0pt, weak: true)
}

The one at the end isn’t actually necesssary, you can get the same results by removing the space after your function call. From myhat(x) e to myhat(x)e.

One problem with this solution is that it doesn’t produce the same exact spacing as the normal hat. Here I’ve drawn myhat in red then hat in black and placed them over top of each other. If the two xs were in the same position there would not be any of the red x visible.


Replacing math.ast.triple with math.ast fixes this, so the issue is that symbol is wider than the normal accent. I’m sure there is a way around this but I’m not sure now what that is.


@mkorje, I haven’t gotten your code to produce the correct results. Was it working for you? I tried in the web app with version 0.13.1.
Also sym.last.triple is a typo.

3 Likes

Maybe another option is to use attach:

#let myhat(body) = math.attach(
  math.limits(body),
  t: move(math.ast.triple, dy: 0.1em),
)

$ 
"Built-in hat:"quad & 2 y hat(x) e^j \

"Custom hat:"quad & 2 y myhat(x) e^j \
$

image

3 Likes

Folks, thanks so much for your thoughtful responses! :grin: Plenty of great solutions here. I never thought to use attach with a move command… cool. And the weak spaces idea answers the more general question of how to make my functions not have a space at the beginning.