I have a little puzzle regarding the interaction of metadata and context, and I could use your help in understanding it.
The following code stores the current page number in metadata and then retrieves it on the next page. The following code snippet correctly displays “Page value from metadata: 1” on the second page:
Metadata defined here.
// This displays "page 2"
// #metadata(context counter(page).display()) <mypage> // (a)
// This displays "page 1"
#context [ #metadata(counter(page).display()) <mypage> ] // (b)
#pagebreak()
Page value from metadata: #context {
query(<mypage>).first().value
}
However, if you comment out (b) and uncomment (a), the second page displays “Page value from metadata: 2”.
I understand that the second version evaluates the page counter in the context of the query, not the context of the metadata. But could someone explain to me in detail why this happens?
The short story is that a context value is a movable little packaged “cookie” that will evaluate according to its current location. When you access metadata.value and get a context value, then you take that movable package and place it in a new location. It should in fact evaluate in its own context, not the context of the query.
In the first situation, you store the context block itself, that is you store a piece if content that, as soon as it is placed into the document, will look for the then current page number. You can think of it as a sort of function that gets evaluated when it is placed in the document, but not (yet) when it is stored in a variable or a metadata.
If you query that piece of metadata on multiple pages, it will always show the respective different page numbers.
In the second situation, you store the value of the page counter. The surrounding context block is only evaluated once, at the place where you make the metadata (as this is where it is placed into the document – the fact that you can’t see the metadata is not important for this)
And a distinction I think of too is that context is not a keyword that says “remember this place” but instead it’s just structural, it’s a wrapping for the value it creates. The wrapping travels with the value. Why it needs to be designed like this is because of how typst’s heavily memoizing compiler works. A context-wrapped thing is also a value that can be cached.
In a package I implemented verse line numbers by creating a context value that displays the current line number counter. Then I copy that context value (“cookie”) and place it after every line. So formally the line number was then implemented by placing the same identical typst value after every line.
The explanation of context as a wrapper helps me a lot, thank you.
At the same time, context does not seem to function only as a wrapper. If you drop the #context call around the query in my example, the compiler complains that query “can only be used when context is known”.
So can we say that context is a method for creating context-dependent content? From the outside, it is opaque, but when it is placed in content, it is evaluated and makes the evaluation context available to the code in its body?
So the difference of (a) and (b) in my example would be that (b) evaluates the context-wrapped expression within some content, whereas (a) cannot evaluate the context-wrapped expression (it appears in a place where there is no context), which is why it is passed around as a wrapped value?
And a query expression that is not within a context cannot be evaluated because while there is technically a context in which it is evaluated, that context hasn’t been made available through a context wrapper?
I don’t quite understand this one. Metadata (a) contains a value of context kind (really content is the type and context is the element function if you ask typst). It’s true that it’s not evaluated but I think that’s because typst decides in its implementation that it should not be evaluated, metadata just stores a value, in this case content, doesn’t evaluate anything you store in there.