Hey everyone,
I’m trying to generate a table from an array, where each element is a dictionary holding data for each row. I’d like to add a counter in each row based on a particular field. I searched the forum for similar issues (e.g., How does the counter update function works?) but I couldn’t get it to work.
I have the following reduced example:
#let activity-table(activities) = {
let ctypes = (a: counter("a"), b: counter("b"))
table(
columns: 2,
..activities.map(it => {
let ctype = ctypes.at(it.type)
ctype.step()
return (
[#it.type#context ctype.display()],
it.description,
)
}).flatten()
)
}
#let activities = (
(type: "a", description: lorem(20)),
(type: "b", description: lorem(10)),
(type: "a", description: lorem(15)),
)
#activity-table(activities)
which produces:

I expected it to print a1, b1, a2, and so on.
Thank you.
If you call ctype.step()
inside the returned list/content, the counter will work. I don’t know why this is necessary though, seems to be some technical detail/issue with counters/contexts inside a map?
// replace the lines inside ..activities.map(it => {}) with the following lines
let ctype = ctypes.at(it.type)
return (
[#ctype.step() #it.type#context ctype.display()],
it.description,
)
It has nothing to do with maps. Only counter steps that end up in the document will actually make a step. Counter steps are about placing content, not about a computation with a side effect.
You probably think that mycounter.step(...)
will have the side effect of increasing some state that is stored somewhere. But this is not the case. Typst doesn’t think in terms of computation. That would be a problem, because your function (like activity-table
) might actually be called multiple times under the hood (which would increase the counter multiple times), or not get called at all because it has been memoized from a previous call (so would not increase the counter).
Instead, what happens is that counter.step(...)
actually returns a piece of invisible content which is essentially some metadata that says “step the counter here”. And so what you actually need to do is to place that piece of (invisible) content into the document.
For example, consider:
#let c = counter("test")
#let step = c.step()
#step
#step
#context c.display("1.1")
You might think it only increases the counter once since, after all, step()
is only called once.
But what’s important is the piece of content that step()
returns, which is stored in the step
variable in this case. And if I place it two times, it will in fact increase the counter twice! You could even do #{ 10*step }
.
3 Likes
Thank you. That did work.
Thank you for the explanation. It’s a little bit difficult to grasp, but I think I get it now.