How to `counter.display-at(location)` or get remote context?

How to display the value of a counter at a given location?

Note that the numbering may have changed or depend on the context. Therefore, it looks like that I need the remote context or to time travel. I know I can achieve it for counters and states, but is it possible for context?

To be specific, how to get the original figure.numbering at <fig> in the following example?

#set figure(numbering: "1")

= First
#figure(rect(), caption: [A]) <fig>
#figure(rect(), caption: [B])

- @fig
  // β‡’ βœ… Figure 1
- #context numbering(figure.numbering, ..counter(figure).at(<fig>))
  // β‡’ βœ… 1


= Second
#set figure(numbering: "A") // πŸ‘ˆ The numbering has changed

- @fig
  // β‡’ βœ… Figure 1
- #context numbering(figure.numbering, ..counter(figure).at(<fig>))
  // β‡’ ❌ A

My attempt

I have tried various approaches, and the only two working solutions (in typst 0.13.1) are:

  1. Use the canonical ref and avoid reconstructing it by show ref: it => ….

  2. Alternatively, create a metadata in that context and save the result of counter.display() in it.

    #figure(rect(), caption: [A]) <fig>
    #context [#metadata(counter(figure).display()) <meta>]
    
    // …
    
    #context query(<meta>).first().value  // β‡’ βœ… 1
    

Is it safe to use metadata here?

Full code
#set page(height: auto, width: auto, margin: 2em)

= Setup

#set heading(numbering: "1")
#set figure(numbering: n => raw(
  repr((
    figure: n,
    heading: counter(heading).get(),
  )),
  lang: "typc",
))

#let please-display() = counter(figure).display()
#let please-number(at: none) = {
  assert.ne(at, none)
  numbering(figure.numbering, ..counter(figure).at(at))
}

#let meta-save(label, value) = [#metadata(value) #std.label(label)]

#let reconstruct-ref(body) = {
  show ref: it => {
    let el = it.element
    assert.eq(el.func(), figure)
    numbering(el.numbering, ..counter(figure).at(el.location()))
  }
  body
}
#let show-ref-eq(body) = {
  // https://typst.app/docs/reference/model/ref/#customization
  show ref: it => {
    let eq = math.equation
    let el = it.element
    if el != none and el.func() == eq {
      // Override equation references.
      link(el.location(), numbering(el.numbering, ..counter(eq).at(el.location())))
    } else {
      // Other references as usual.
      it
    }
  }
  body
}

= First
#figure(rect(), caption: [A]) <fig>

#let numbered = context please-number(at: here())
#let displayed = context please-display()

#context meta-save("numbered", please-number(at: here()))
#context meta-save("displayed", please-display())

#figure(rect(), caption: [B])

- @fig --- βœ… Ref
- #reconstruct-ref[@fig] --- βœ… Customized and reconstructed ref
- #show-ref-eq[@fig] --- βœ… Customized but canonical ref
- #context please-number(at: <fig>) --- βœ… Please number there
- #context please-number(at: here()) --- ❌ Please number here
- #numbered --- ❌ Numbered
- #context query(<numbered>).first().value --- βœ… Numbered in metadata
- #context please-display() --- ❌ Please display
- #displayed --- ❌ Displayed
- #context query(<displayed>).first().value --- βœ… Displayed in metadata

= Second
#set figure(numbering: "A")
#counter(figure).update(26)

```typ
#set figure(numbering: "A")
#counter(figure).update(26)
```

- @fig --- βœ… Ref
- #reconstruct-ref[@fig] --- ❌ Customized and reconstructed ref
- #show-ref-eq[@fig] --- βœ… Customized but canonical ref
- #context please-number(at: <fig>) --- ❌ Please number there
- #context please-number(at: here()) --- ❌ Please number here
- #numbered --- ❌ Numbered
- #context query(<numbered>).first().value --- βœ… Numbered in metadata
- #context please-display() --- ❌ Please display
- #displayed --- ❌ Displayed
- #context query(<displayed>).first().value --- βœ… Displayed in metadata

Besides, I notice the function Counter::display_at_loc in rust. Could I access it in typst?

Previous discussions on Discord

laurmaedje β€” 2023/6/4 17:47
counter(page).display-at(loc) would be useful
Discord

:fire:PgSuper​:fire: β€” 2023/12/30 02:58
there has been a proposal to add something like counter(page).display(at: loc)
Discord

Background

I met Referencing `dependent-numbering` counter in other section gives wrong number Β· Issue #1 Β· jbirnick/typst-headcount Β· GitHub.

The full code of headcount 0.1.0 – Typst Universe is as the following. Here the numbering depends on the context.

headcount 0.1.0 lib.typ (~10 lines)
#let reset-counter(counter, levels: 1) = it => {
  if it.level <= levels { counter.update((0,)) }
  it
}

#let normalize-length(array, length) = {
  if array.len() > length {
    array = array.slice(0, length)
  } else if array.len() < length {
    array += (length - array.len()) * (0,)
  }

  return array
}

#let dependent-numbering(style, levels: 1) = n => {
  numbering(style, ..normalize-length(counter(heading).get(), levels), n)
}
More minimal working examples…
#import "@preview/headcount:0.1.0": dependent-numbering, reset-counter
#set figure(numbering: dependent-numbering("1-1"))
#show heading: reset-counter(counter(figure.where(kind: image)))

#import "@preview/great-theorems:0.1.2": great-theorems-init, mathblock
#show: great-theorems-init
#let theorem = mathblock(blocktitle: [Theorem], counter: counter("theorem"), numbering: dependent-numbering("1-1"))
#show heading: reset-counter(counter("theorem"))
1 Like

Finally, I get rid of show ref.

#import "@preview/headcount:0.1.0": dependent-numbering

#set heading(numbering: "1.1")

#let neo-figure = figure.where(kind: "neo")
#let neo(body) = figure(
  kind: "neo",
  supplement: none,
  {
    [Neo ]
    context counter(neo-figure).display(dependent-numbering("1.1.1", levels: 2))
    [: ]
    body
    [ ]
    context counter(neo-figure).display()
  },
)
#show neo-figure: set figure(
  numbering: dependent-numbering("{1.1.1}", levels: 2),
)
#show neo-figure: set align(start)
#show heading: it => it + counter(neo-figure).update(0)

= First

#neo(lorem(5)) <1>
#neo(lorem(5)) <2>

== First first

#neo(lorem(5)) <3>

#counter(neo-figure).update(9)
#neo(lorem(5)) <3.5>

#counter(heading).update(2)
= Second

#neo(lorem(5)) <4>

@1, @2, @3, @3.5, @4.

You can get at the previous value of figure.numbering by querying the labelled figure itself:

#context numbering(query(<fig>).first().numbering, ..counter(figure).at(<fig>))

will display the figure counter with the value and numbering it had at <fig>.

You can also wrap this in a helper function:

#let counter-at(target) = context {
  let target = query(target).first()
  numbering(target.numbering, ..counter(target.func()).at(target.location()))
}
#counter-at(<fig>)
1 Like

Thank you nleanba!

Unfortunately, query(…) only works for changed numbering, but not for context-sensitive numbering, e.g., the following.

#set heading(numbering: "1")
#set figure(
  numbering: (..nums) => numbering("1.1", ..counter(heading).get(), ..nums),
)

= First
#figure(
  rect(),
  caption: [A],
) <fig>

= Second

@fig

#context numbering(
  query(<fig>).first().numbering,
  ..counter(figure).at(<fig>),
)

yeah, fair enough.

In my theoretic package (for theorems) I simply store the rendered number (which may include chapter numbers) in a metadata (along with other stuff like title etc.) – that also works, but it requires the set-up of creating the metadata in the figure.

1 Like

Well I haven’t developed any package on theorems or counters… I just want a simple clean way that will not conflict with any other packages.

I still think my previous post is the best way for now.
The key is to replace any counter("…") with counter(figure.where(kind: "…")), eliminating the need of show ref.

2 Likes

There have been related discussions on Discord recently.

The tracking issue: