How to create something like #emph, but it jumps to a certain place in the pdf?

How can I create something like #emph, where, when I press on it in the final pdf, it jumps to where it was mentioned first in maybe a #define thing in the pdf?

Kinda like refs, but a little different

== Definition (strict)
A function is called #define[strict], if $epi(f)$ is a strict set

== Example
$f(x) := x^2$ is a #word[strict] function

You can create links with the link function, and generate a label for each definition using the label function:

#let define(it) = [#emph(it)#label("define:" + it.text)]
#let word(it) = link(label("define:" + it.text), emph(it))

#let epi = math.op("epi")

== Definition (strict)
A function is called #define[strict], if $epi(f)$ is a strict set

#pagebreak()

== Example
$f(x) := x^2$ is a #word[strict] function

Ok that works pretty well, but could you also somehow just make a function which creates a label and then just reference it normally?

I tried ti do it myself, but I can’t get around the limit that you can’t label text
Is there something invisible you can reference? That would be extremely helpful

Can you show a minimal example of what you tried that doesn’t work?

I tried a lot of things, but mainly just figures (I sadly don’t have the code anymore as it was very frustrating), but these had the problem that they caused newlines and when I hid them, the reference didn’t work because it was attached to nothing.

I also prompted ChatGPT and Gemini, but they tried to use metadata(), but this also can’t be referenced.

Basically the only thing I need is either an invisible figure I can reference and then actually use the reference or the ability to reference words.

With a show rule on ref, you can allow references on all kinds of elements, so you can do something like this.

#let define(word) = emph[#word#label(word.text)]

#show ref: it => {
  if query(it.target) == ([#str(it.target)],) {
    // The only element with the target label is the
    // definition text containing the label name, so
    // just show and link to that definition.
    emph(link(it.target, str(it.target)))
  } else {
    it
  }
}

Then, you can combine the #define[..] function call with normal references like this:

== Definition (strict)
A function is called #define[strict], if $epi(f)$ is a strict set

== Example
$f(x) := x^2$ is a @strict function
2 Likes

Thanks that works great, now how can I add something like an empty reference? I tried to add an input parameter and that worked, but if I understand your logic correctly, it won’t work when every invisible Label has the same invisible character

I’m not sure what to imagine by an empty reference, as there has to be some way you want to identify what to link to.

Inversion condition makes it shorter and easier to read (well, not the ([#str(it.target)],) part):

#show ref: it => {
  if query(it.target) != ([#str(it.target)],) { return it }
  // The only element with the target label is the
  // definition text containing the label name, so
  // just show and link to that definition.
  emph(link(it.target, str(it.target)))
}

Like an empty character I can put somewhere with a reference

Actually, how could you make this work with stuff with space in them (for example in my case “strict convex”), I tried replacing " " with “-” and then converting it back but I’m not sure how to implement that logic

You can’t use sugar syntax for referencing such labels.

#figure[] #label(" ")
#ref(label(" "))

I see, that is also possible. Here is a more robust (and slightly more complex) way that allows arbitrary content in definitions and automatically attaches usable labels (with hyphens instead of spaces and other non-letter characters):

#let to-label(body) = {
  // Convert body to string, replace non-letter characters with
  // hyphens, and ensure that it doesn't end with a hyphen.
  import "@preview/t4t:0.4.2": get
  label(get.text(body)
    .replace(regex("[^\p{L}\p{N}\-]+"), "-")
    .trim("-", at: end))
}

#let define(word) = emph[#word#to-label(word)]

#show ref: it => {
  // Elements that are referencable by default and whose
  // reference appearance should not be overridden.
  let referencable = (figure, math.equation, heading, footnote)
  
  if it.element == none { return it }
  if it.element.func() in referencable { return it }
  if it.target != to-label(it.element) { return it }
  // If the target element's body matches the label, show the
  // reference as the element's body linked to the definition.
  emph(link(it.target, it.element))
}

This would allow referencing something like #define[strict convex] with @strict-convex.

Quick note why referencable elements are excluded

To not intefere with standard references, elements which are already referencable are excluded. For example, in the case

#figure(rect[Hello], caption: [A rectangle.]) <Hello>

the label <Hello> matches the text inside the rectangle, so the figure would have mistakenly been treated as a definition, replacing the “Figure 1” with a copy of the figure itself.

1 Like

And could there be a way to reference something invisible?
Like I tried it with a figure, but then the ref part doesn’t work anymore

Actually nevermind, I got it to work with

show ref: it => {
    if it.element.has("body") and it.element.body == [ ] {
      return emph(link(it.target, label-to-string(it.target)), bold: false)
    } 
    
    let referencable = (figure, math.equation, heading, footnote)
    
    if it.element == none { return it }
    if it.element.func() in referencable { return it }
    if it.target != to-label(it.element) { return it }
    
    emph(link(it.target, it.element), bold: false)
  }

and

#let to-label(body) = {
  // Convert body to string, replace non-letter characters with
  // hyphens, and ensure that it doesn't end with a hyphen.
  import "@preview/t4t:0.4.2": get
  label(get.text(body)
    .replace(regex("[^\p{L}\p{N}\-]+"), "-")
    .trim("-", at: end))
}

#let label-to-string(string) = str(string).replace("-", " ")

#let define(word, hide: false) = {
  if hide {
    [
      #figure(
        [ ],
      )
      #to-label(word)
      #v(0fr)
    ]
    // emph[#word#to-label(word)]
  } else {
    emph[#word#to-label(word)]
  }
}