diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-02-27 11:05:16 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-27 10:05:16 +0000 |
| commit | 145723b1ef4fa23f1f6665b8907dfe79d0bf83cf (patch) | |
| tree | 02a7de661ddd5dafa75dfce3e3c8b45a7333b9dc /docs | |
| parent | e9ee00a7c0df083663ff5ccca162238b88525e14 (diff) | |
New context system (#3497)
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/guides/guide-for-latex-users.md | 4 | ||||
| -rw-r--r-- | docs/guides/page-setup.md | 45 | ||||
| -rw-r--r-- | docs/reference/context.md | 235 | ||||
| -rw-r--r-- | docs/reference/styling.md | 10 | ||||
| -rw-r--r-- | docs/reference/syntax.md | 3 |
5 files changed, 267 insertions, 30 deletions
diff --git a/docs/guides/guide-for-latex-users.md b/docs/guides/guide-for-latex-users.md index 4b8c43b1..d991ef78 100644 --- a/docs/guides/guide-for-latex-users.md +++ b/docs/guides/guide-for-latex-users.md @@ -603,7 +603,7 @@ The example below This should be a good starting point! If you want to go further, why not create a reusable template? -## Bibliographies { #bibliographies } +## Bibliographies Typst includes a fully-featured bibliography system that is compatible with BibTeX files. You can continue to use your `.bib` literature libraries by loading them with the [`bibliography`]($bibliography) function. Another @@ -627,7 +627,7 @@ use in prose (cf. `\citet` and `\textcite`) are available with You can find more information on the documentation page of the [`bibliography`]($bibliography) function. -## Installation { #installation } +## Installation You have two ways to use Typst: In [our web app](https://typst.app/signup/) or by [installing the compiler](https://github.com/typst/typst/releases) on your computer. When you use the web app, we provide a batteries-included diff --git a/docs/guides/page-setup.md b/docs/guides/page-setup.md index 046bf9f4..a70f025f 100644 --- a/docs/guides/page-setup.md +++ b/docs/guides/page-setup.md @@ -174,13 +174,13 @@ conditionally remove the header on the first page: ```typ >>> #set page("a5", margin: (x: 2.5cm, y: 3cm)) -#set page(header: locate(loc => { - if counter(page).at(loc).first() > 1 [ +#set page(header: context { + if counter(page).get().first() > 1 [ _Lisa Strassner's Thesis_ #h(1fr) National Academy of Sciences ] -})) +}) #lorem(150) ``` @@ -206,12 +206,12 @@ such a label exists on the current page: ```typ >>> #set page("a5", margin: (x: 2.5cm, y: 3cm)) -#set page(header: locate(loc => { - let page-counter = counter(page) - let matches = query(<big-table>, loc) - let current = page-counter.at(loc) +#set page(header: context { + let page-counter = + let matches = query(<big-table>) + let current = counter(page).get() let has-table = matches.any(m => - page-counter.at(m.location()) == current + counter(page).at(m.location()) == current ) if not has-table [ @@ -291,7 +291,7 @@ a custom footer with page numbers and more. ```example >>> #set page("iso-b6", margin: 1.75cm) -#set page(footer: [ +#set page(footer: context [ *American Society of Proceedings* #h(1fr) #counter(page).display( @@ -314,21 +314,20 @@ circle for each page. ```example >>> #set page("iso-b6", margin: 1.75cm) -#set page(footer: [ +#set page(footer: context [ *Fun Typography Club* #h(1fr) - #counter(page).display(num => { - let circles = num * ( - box(circle( - radius: 2pt, - fill: navy, - )), - ) - box( - inset: (bottom: 1pt), - circles.join(h(1pt)) - ) - }) + #let (num,) = counter(page).get() + #let circles = num * ( + box(circle( + radius: 2pt, + fill: navy, + )), + ) + #box( + inset: (bottom: 1pt), + circles.join(h(1pt)) + ) ]) This page has a custom footer. @@ -382,7 +381,7 @@ page counter, you can use the [`page`]($locate) method on the argument of the // This returns one even though the // page counter was incremented by 5. -#locate(loc => loc.page()) +#context here().page() ``` You can also obtain the page numbering pattern from the `{locate}` closure 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) | |
