summaryrefslogtreecommitdiff
path: root/docs/reference
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-02-27 11:05:16 +0100
committerGitHub <noreply@github.com>2024-02-27 10:05:16 +0000
commit145723b1ef4fa23f1f6665b8907dfe79d0bf83cf (patch)
tree02a7de661ddd5dafa75dfce3e3c8b45a7333b9dc /docs/reference
parente9ee00a7c0df083663ff5ccca162238b88525e14 (diff)
New context system (#3497)
Diffstat (limited to 'docs/reference')
-rw-r--r--docs/reference/context.md235
-rw-r--r--docs/reference/styling.md10
-rw-r--r--docs/reference/syntax.md3
3 files changed, 243 insertions, 5 deletions
diff --git a/docs/reference/context.md b/docs/reference/context.md
new file mode 100644
index 00000000..5ebcfbe7
--- /dev/null
+++ b/docs/reference/context.md
@@ -0,0 +1,235 @@
+---
+description: |
+ How to deal with content that reacts to its location in the document.
+---
+
+# Context
+Sometimes, we want to create content that reacts to its location in the
+document. This could be a localized phrase that depends on the configured text
+language or something as simple as a heading number which prints the right
+value based on how many headings came before it. However, Typst code isn't
+directly aware of its location in the document. Some code at the beginning of
+the source text could yield content that ends up at the back of the document.
+
+To produce content that is reactive to its surroundings, we must thus
+specifically instruct Typst: We do this with the `{context}` keyword, which
+precedes an expression and ensures that it is computed with knowledge of its
+environment. In return, the context expression itself ends up opaque. We cannot
+directly access whatever results from it in our code, precisely because it is
+contextual: There is no one correct result, there may be multiple results in
+different places of the document. For this reason, everything that depends on
+the contextual data must happen inside of the context expression.
+
+Aside from explicit context expressions, context is also established implicitly
+in some places that are also aware of their location in the document:
+[Show rules]($styling/#show-rules) provide context[^1] and numberings in the
+outline, for instance, also provide the proper context to resolve counters.
+
+## Style context
+With set rules, we can adjust style properties for parts or the whole of our
+document. We cannot access these without a known context, as they may change
+throughout the course of the document. When context is available, we can
+retrieve them simply by accessing them as fields on the respective element
+function.
+
+```example
+#set text(lang: "de")
+#context text.lang
+```
+
+As explained above, a context expression is reactive to the different
+environments it is placed into. In the example below, we create a single context
+expression, store it in the `value` variable and use it multiple times. Each use
+properly reacts to the current surroundings.
+
+```example
+#let value = context text.lang
+#value
+
+#set text(lang: "de")
+#value
+
+#set text(lang: "fr")
+#value
+```
+
+Crucially, upon creation, `value` becomes opaque [content]($content) that we
+cannot peek into. It can only be resolved when placed somewhere because only
+then the context is known. The body of a context expression may be evaluated
+zero, one, or multiple times, depending on how many different places it is put
+into.
+
+## Location context
+Context can not only give us access to set rule values. It can also let us know
+_where_ in the document we currently are, relative to other elements, and
+absolutely on the pages. We can use this information to create very flexible
+interactions between different document parts. This underpins features like
+heading numbering, the table of contents, or page headers dependant on section
+headings.
+
+Some functions like [`counter.get`]($counter.get) implicitly access the current
+location. In the example below, we want to retrieve the value of the heading
+counter. Since it changes throughout the document, we need to first enter a
+context expression. Then, we use `get` to retrieve the counter's current value.
+This function accesses the current location from the context to resolve the
+counter value. Counters have multiple levels and `get` returns an array with the
+resolved numbers. Thus, we get the following result:
+
+```example
+#set heading(numbering: "1.")
+
+= Introduction
+#lorem(5)
+
+#context counter(heading).get()
+
+= Background
+#lorem(5)
+
+#context counter(heading).get()
+```
+
+For more flexibility, we can also use the [`here`]($here) function to directly
+extract the current [location]($location) from the context. The example below
+demonstrates this:
+
+- We first have `{counter(heading).get()}`, which resolves to `{(2,)}` as
+ before.
+- We then use the more powerful [`counter.at`]($counter.at) with
+ [`here`]($here), which in combination is equivalent to `get`, and thus get
+ `{(2,)}`.
+- Finally, we use `at` with a [label]($label) to retrieve the value of the
+ counter at a _different_ location in the document, in our case that of the
+ introduction heading. This yields `{(1,)}`. Typst's context system gives us
+ time travel abilities and lets us retrieve the values of any counters and
+ states at _any_ location in the document.
+
+```example
+#set heading(numbering: "1.")
+
+= Introduction <intro>
+#lorem(5)
+
+= Background <back>
+#lorem(5)
+
+#context [
+ #counter(heading).get() \
+ #counter(heading).at(here()) \
+ #counter(heading).at(<intro>)
+]
+```
+
+As mentioned before, we can also use context to get the physical position of
+elements on the pages. We do this with the [`locate`]($locate) function, which
+works similarly to `counter.at`: It takes a location or other
+[selector]($selector) that resolves to a unique element (could also be a label)
+and returns the position on the pages for that element.
+
+```example
+Background is at: \
+#context locate(<back>).position()
+
+= Introduction <intro>
+#lorem(5)
+#pagebreak()
+
+= Background <back>
+#lorem(5)
+```
+
+There are other functions that make use of the location context, most
+prominently [`query`]($query). Take a look at the
+[introspection]($category/introspection) category for more details on those.
+
+## Nested contexts
+Context is also accessible from within function calls nested in context blocks.
+In the example below, `foo` itself becomes a contextual function, just like
+[`to-absolute`]($length.to-absolute) is.
+
+```example
+#let foo() = 1em.to-absolute()
+#context {
+ foo() == text.size
+}
+```
+
+Context blocks can be nested. Contextual code will then always access the
+innermost context. The example below demonstrates this: The first `text.lang`
+will access the outer context block's styles and as such, it will **not**
+see the effect of `{set text(lang: "fr")}`. The nested context block around the
+second `text.lang`, however, starts after the set rule and will thus show
+its effect.
+
+```example
+#set text(lang: "de")
+#context [
+ #set text(lang: "fr")
+ #text.lang \
+ #context text.lang
+]
+```
+
+You might wonder why Typst ignores the French set rule when computing the first
+`text.lang` in the example above. The reason is that, in the general case, Typst
+cannot know all the styles that will apply as set rules can be applied to
+content after it has been constructed. Below, `text.lang` is already computed
+when the template function is applied. As such, it cannot possibly be aware of
+the language change to French in the template.
+
+```example
+#let template(body) = {
+ set text(lang: "fr")
+ upper(body)
+}
+
+#set text(lang: "de")
+#context [
+ #show: template
+ #text.lang \
+ #context text.lang
+]
+```
+
+The second `text.lang`, however, _does_ react to the language change because
+evaluation of its surrounding context block is deferred until the styles for it
+are known. This illustrates the importance of picking the right insertion point for a context to get access to precisely the right styles.
+
+The same also holds true for the location context. Below, the first
+`{c.display()}` call will access the outer context block and will thus not see
+the effect of `{c.update(2)}` while the second `{c.display()}` accesses the inner context and will thus see it.
+
+```example
+#let c = counter("mycounter")
+#c.update(1)
+#context [
+ #c.update(2)
+ #c.display() \
+ #context c.display()
+]
+```
+
+## Compiler iterations
+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]($state/#caution)). If Typst cannot resolve everything within five
+attempts, it will stop and output the warning "layout did not converge within 5
+attempts."
+
+A very careful reader might have noticed that not all of the functions presented
+above actually make use of the current location. While
+`{counter(heading).get()}` definitely depends on it,
+`{counter(heading).at(<intro>)}`, for instance, does not. However, it still
+requires context. While its value is always the same _within_ one compilation
+iteration, it may change over the course of multiple compiler iterations. If one
+could call it directly at the top level of a module, the whole module and its
+exports could change over the course of multiple compiler iterations, which
+would not be desirable.
+
+[^1]: Currently, all show rules provide styling context, but only show rules on
+ [locatable]($location/#locatable) elements provide a location context.
diff --git a/docs/reference/styling.md b/docs/reference/styling.md
index 687fed79..1c3fd173 100644
--- a/docs/reference/styling.md
+++ b/docs/reference/styling.md
@@ -81,9 +81,9 @@ in Typst. For maximum flexibility, you can instead write a show rule that
defines how to format an element from scratch. To write such a show rule,
replace the set rule after the colon with an arbitrary [function]($function).
This function receives the element in question and can return arbitrary content.
-Different [fields]($scripting/#fields) are available on the element passed to
-the function. Below, we define a show rule that formats headings for a fantasy
-encyclopedia.
+The available [fields]($scripting/#fields) on the element passed to the function
+again match the parameters of the respective element function. Below, we define
+a show rule that formats headings for a fantasy encyclopedia.
```example
#set heading(numbering: "(I)")
@@ -91,7 +91,9 @@ encyclopedia.
#set align(center)
#set text(font: "Inria Serif")
\~ #emph(it.body)
- #counter(heading).display() \~
+ #counter(heading).display(
+ it.numbering
+ ) \~
]
= Dragon
diff --git a/docs/reference/syntax.md b/docs/reference/syntax.md
index 643dc954..40213fe9 100644
--- a/docs/reference/syntax.md
+++ b/docs/reference/syntax.md
@@ -12,7 +12,7 @@ All this is backed by a tightly integrated scripting language with built-in and
user-defined functions.
## Modes
-Typst has three syntactical modes: Markup, math, and code. Markup mode is the
+Typst has three syntactical modes: Markup, math, and code. Markup mode is the
default in a Typst document, math mode lets you write mathematical formulas, and
code mode lets you use Typst's scripting features.
@@ -111,6 +111,7 @@ a table listing all syntax that is available in code mode:
| Show-set rule | `{show par: set block(..)}` | [Styling]($styling/#show-rules) |
| Show rule with function | `{show raw: it => {..}}` | [Styling]($styling/#show-rules) |
| Show-everything rule | `{show: columns.with(2)}` | [Styling]($styling/#show-rules) |
+| Context expression | `{context text.lang}` | [Context]($context) |
| Conditional | `{if x == 1 {..} else {..}}` | [Scripting]($scripting/#conditionals) |
| For loop | `{for x in (1, 2, 3) {..}}` | [Scripting]($scripting/#loops) |
| While loop | `{while x < 10 {..}}` | [Scripting]($scripting/#loops) |