What is the best way to get the total number of pages in a document and insert in the document?

I have an exam paper that I am typesetting in Typst, and I want the cover page to indicate the total number of pages (e.g. “This question and answer book contains 29 pages”).

It took longer than I had hoped to get working syntax, and in the end I came up with the following:

This question and answer book contains 
#context[#counter(page).final().at(0)] pages

Is this the best way to generate this text, or is there a more natural alternative which I am overlooking?

Also, not that I understand the concept of context particularly, but the documentation for Context starts with

Sometimes, we want to create content that reacts to its location in the document.

But in this case, I would have assumed that the total number of pages in the document is not contingent on the location in the document. What am I missing here?

Hello Bryn,
You can get the number of pages in the document using display(both: true):

#context {
  counter(page).display("1/1", both: true)
}

Getting your result is a matter of splitting the string

#context [
This question and answer book contains
#counter(page).display("1/1", both: true).split("/").at(-1)
pages
]

The final call should be deprecated soon (I believe), so the best way is to use display, to which you can pass a numbering.

See counter(both: bool).

1 Like

Oh dear, this seems even more obfuscated than my first attempt! Is your recommendation that I should create a larger string that also includes the current page number, which I then remove?

I couldn’t see any reference to final() being deprecated but I’m hoping that the ability to “time travel” isn’t removed, as I also want to be able to “time travel” to the end of the document and know the total number of marks (which is stored in a counter).

I think I might have misread, I am very sorry! I think your attempt is the best one, and what I gave should probably not be used, unless you need both first and final values.

The part I misread in the documentation was

Compatibility: This argument only exists for compatibility with Typst 0.10 and lower and shouldn’t be used anymore.

which referred only to the location argument of final, and not of final itself.

2 Likes

Yes, I also found that compatibility note confusing too but I concluded that it used to be required to pass the location but now it stands for the final location in the document.

I must say, it still seems a bit over-the-top to have to put the context etc. in, just to get the final page number.

The value here is really not dependent on the location, but the documentation simplifies a bit here which is confusing in this particular instance.

The final counter value depends on the document as a whole, so it can’t be evaluated immediately; only after the whole document was put together. context allows Typst to delay the execution, or even execute multiple times – imagine the counter was so big that it caused an extra page to appear in the document. In that case Typst evaluates the context again with the new value.

Each expression in Typst needs some definitive value, and if counter.final() returned a number without context, that would not work out.

1 Like

Yes, but you can optimize/simplify it like this:

#context counter(page).final().first()

The key part here is “sometimes”. This does not apply to your example, but does for this:

#context here().position()

image

1 Like

That makes good sense, thanks! I hadn’t thought about the fact that not only can Typst not calculate it yet but it needs to find a fixed point if it’s a rather large document (or I print the page number in such a large font) and the printing of the final page number causes the page count to increase. Maybe I should think of context as for the type of thing that would require a second run of a LaTeX compiler, if I were typesetting the document in LaTeX.

I think that’s a pretty good mental model. It also gives a feeling for the dreaded “layout did not converge within 5 attempts” warning: that’s how often Typst will try to recompile before giving up.

btw, don’t forget to tick one of the responses if you got a satisfying answer. The answer you choose should usually be the response that you found most correct/helpful/comprehensive for the question you asked.

2 Likes

Typst indeed uses the same tactic as LaTeX, but the difference is that with LaTeX you have to manually re-run the compiler, while Typst does this automatically. And if something needs more than 2 compilations to converge, it provides 5 attempts for such documents.

From Context – Typst Documentation :

To resolve contextual interactions, the Typst compiler processes your document multiple times. For instance, to resolve a locate call, Typst first provides a placeholder position, layouts your document and then recompiles with the known position from the finished layout. The same approach is taken to resolve counters, states, and queries. In certain cases, Typst may even need more than two iterations to resolve everything. While that’s sometimes a necessity, it may also be a sign of misuse of contextual functions (e.g. of state). If Typst cannot resolve everything within five attempts, it will stop and output the warning “layout did not converge within 5 attempts.”