Development build 586e1bd4
Typst文档简体中文版v0.15.1 + dev 586e1bd4

上下文

有时,我们希望创建响应其文档位置的内容。这可能是一个依赖于配置文本语言的本地化短语,或者像标题编号这样简单的东西,它根据前面有多少标题打印正确的值。然而,Typst代码本身并不直接意识到它在文档中的位置。源文本开头的某些代码可能会产生位于文档末尾的内容。

为了产生响应其周围环境的内容,我们必须明确地指示Typst:我们使用context关键字,它前面是一个表达式,并确保它以已知的环境计算。作为回报,上下文表达式本身变得不透明。我们无法直接访问来自它的任何结果,因为它是上下文的:没有一个是正确的结果,可能会有多个结果在文档的不同地方。因此,所有依赖于上下文数据的内容必须发生在上下文表达式内部。

除了显式上下文表达式,上下文还在一些地方隐式建立:show规则提供上下文1,例如大纲中的编号,也提供正确的上下文来解析计数器。 [⚠️]

样式上下文

使用set规则,我们可以调整部分或整个文档的样式属性。我们无法在没有已知上下文的情况下访问这些属性,因为它们可能会在整个文档过程中发生变化。当上下文可用时,我们可以简单地通过访问相应元素函数上的字段来检索它们。 [⚠️]

#set text(lang: "de") #context text.lang

如上所述,上下文表达式对它被放置的不同环境是反应性的。在下面的示例中,我们创建一个单一的上下文表达式,将其存储在value变量中,并多次使用它。每次使用都正确地响应当前环境。 [⚠️]

#let value = context text.lang #value #set text(lang: "de") #value #set text(lang: "fr") #value

至关重要的是,在创建时,value变成一个不透明的内容,我们无法窥视。它只能在放置在某个地方时才能解析,因为只有那时上下文才被知道。上下文表达式的内容可能被评估零次、一次或多次,这取决于它被放置了多少个不同的地方。 [⚠️]

位置上下文

我们已经看到上下文给了我们访问show规则值的权限。但它还可以做更多:它还让我们知道文档中哪里相对于其他元素,以及绝对在页面上。我们可以使用这些信息来创建非常灵活的不同文档部分之间的交互。这支持了像标题编号、目录或依赖于部分标题的页面标题等特性。

一些函数,如counter.get隐式访问当前位置。在下面的示例中,我们想要检索标题计数器的值。由于它在整个文档中发生变化,我们需要首先进入一个上下文表达式。然后,我们使用 get 来检索计数器的当前值。该函数从上下文中访问当前位置来解析计数器值。计数器有多个级别,get 返回一个包含解析数字的数组。因此,我们得到以下结果: [⚠️]

#set heading(numbering: "1.") = Introduction #lorem(5) #context counter(heading).get() = Background #lorem(5) #context counter(heading).get()

为了更大的灵活性,我们还可以使用here函数直接从上下文中提取当前位置。下面的示例演示了这一点:

[⚠️]

#set heading(numbering: "1.") = Introduction <intro> #lorem(5) = Background <back> #lorem(5) #context [ #counter(heading).get() \ #counter(heading).at(here()) \ #counter(heading).at(<intro>) ]

我们还可以使用上下文来获取元素在页面上的物理位置。我们使用locate函数来做到这一点,它类似于counter.at:它接受一个位置或其他选择器,解析为一个唯一的元素(也可以是标签),并返回该元素在页面上的位置。 [⚠️]

Background is at: \ #context locate(<back>).position() = Introduction <intro> #lorem(5) #pagebreak() = Background <back> #lorem(5)

还有其他函数利用位置上下文,最突出的是query。查看内省类别以获取更多详细信息。 [⚠️]

嵌套上下文[⚠️]

上下文也可以从嵌套在上下文块中的函数调用中访问。在下面的示例中,foo本身成为上下文函数,就像to-absolute一样。 [⚠️]

#let foo() = 1em.to-absolute() #context { foo() == text.size }

上下文块可以嵌套。上下文代码将始终访问最内层的上下文。下面的示例演示了这一点:第一个text.lang将访问外部上下文块的样式,因此它不会看到set text(lang: "fr")的效果。然而,第二个text.lang周围的嵌套上下文块在show规则之后开始,因此将显示其效果。 [⚠️]

#set text(lang: "de") #context [ #set text(lang: "fr") #text.lang \ #context text.lang ]

您可能想知道为什么Typst在计算上面的第一个text.lang时忽略了法语show规则。原因是,在一般情况下,Typst无法知道所有将作为show规则应用的内容。下面,当模板函数被应用时,text.lang已经计算出来了。因此,它不可能知道模板中的法语变化。 [⚠️]

#let template(body) = { set text(lang: "fr") upper(body) } #set text(lang: "de") #context [ #show: template #text.lang \ #context text.lang ]

第二个text.lang确实对语言变化有反应,因为它的周围上下文块的评估被推迟到样式已知时。这说明了选择正确的插入点以获得精确的正确样式的上下文的重要性。

位置上下文也是如此。下面,第一个c.display()调用将访问外部上下文块,因此不会看到c.update(2)的效果,而第二个c.display()访问内部上下文,因此会看到它。 [⚠️]

#let c = counter("mycounter") #c.update(1) #context [ #c.update(2) #c.display() \ #context c.display() ]

编译器迭代[⚠️]

为了解析上下文交互,Typst编译器多次处理您的文档。例如,为了解析locate调用,Typst首先提供一个占位符位置,布局您的文档,然后重新编译已知位置的布局。同样的方法用于解析计数器、状态和查询。在某些情况下,Typst甚至可能需要超过两次迭代来解析所有内容。虽然有时这是必要的,但它也可能是上下文函数使用不当的迹象(例如,状态)。如果Typst无法在五次尝试内解析所有内容,它将停止并输出警告“布局在五次尝试内未收敛”。

一个非常仔细的读者可能已经注意到,上面提到的所有函数实际上并没有充分利用当前位置。虽然counter(heading).get()肯定依赖于它,但counter(heading).at(<intro>)则不然。然而,它仍然需要上下文。虽然它的值在一次编译迭代中总是相同的,但它可能会在多次编译迭代过程中发生变化。如果它可以直接在模块的顶层调用,整个模块及其导出可能会在多次编译迭代过程中发生变化,这并不是我们想要的。 [⚠️]

  1. 1目前,所有show规则都提供样式上下文,但只有可定位元素的显示规则提供位置上下文。