Typst 的使用

简要介绍

如果你是从 Manim 迁移过来的用户,你可能对 LaTeX 有一定的了解。

当我们在 JAnim 中使用 Typst,就像是在 Manim 中使用 LaTeX,可以通过代码的形式创建排版或公式。

我们常用 TypstTextTypstMath 来在 JAnim 中使用 Typst

typ1 = TypstText(R'This is a #text(aqua)[sentence] with a math expression $cos^2 x + sin^2 x = 1$')
typ2 = TypstMath(R'cos^2 theta + sin^2 theta = 1')

小技巧

当你使用了 VS Code 插件 janim-toolbox,出现在 TypstText 中的 Raw-字符串 (类似 R'...' 的形式)会作为 Typst 代码高亮颜色,如下图所示

../_images/TypstHighlight.png

较为遗憾的是,在 TypstMath 中的 Raw-字符串 暂时没有该功能。

如果你更倾向于使用 JAnim 展示公式,那么你更可能会经常用到 TypstMath

Typst 物件的类型

Typst 物件分为三种:

  • TypstText 表示 Typst 文字,传入的字符串不会被 $ $ 所包裹

  • TypstMath 表示 Typst 公式,传入的字符串会被包裹在 $ $ 中作为公式进行编译

  • TypstDoc 是所有其它 Typst 物件的基类,它表示一个 Typst 文档

    TypstDocTypstTextTypstMath 不同,它会自动与画面的最上方对齐,有一种“从文档的开头开始查看”的感觉

也就是说,TypstTextTypstMath 的区别仅是是否被包裹在公式环境中,例如 TypstMath('x^2')TypstText('$ x^2 $') 在大多数情况下是等效的

Typst 子物件索引

基础用法

对于普通的物件,使用 子物件选择 比较方便;而对于 Typst 物件,有更加方便的索引方式: 字符索引

比如对于 TypstMath 对象

typ = TypstMath('cos^2 theta + sin^2 theta = 1')

可以使用 typ['cos'] 得到 cos 对应的部分,这样你就可以使用类似于 typ['cos'].set(color=BLUE) 的方式进行着色,或进行其它处理。

当出现多个匹配时的处理

你应该注意到了这里有两个“θ”(theta),当你使用 typ['theta'] 的方式进行索引时,将会取出第一个匹配的 θ,也就是前一个。

因为他们是从 01、... 依次编号的,所以在这个例子中你可以使用 typ['theta', 1] 得到后一个。

备注

这也意味着 t['theta']t['theta', 0] 是等效的

TypstColorizeExample
typ = TypstMath('cos^2 theta + sin^2 theta = 1', scale=3).show()

self.forward()
self.play(typ['cos'].anim.set(color=BLUE))
self.play(typ['sin'].anim.set(color=BLUE))
self.play(typ['theta', 0].anim.set(color=GOLD))
self.play(typ['theta', 1].anim.set(color=ORANGE))
self.forward()
self.play(typ['theta', ...].anim.set(color=GREEN))
self.play(typ['space^2', ...].anim.set(color=RED))
self.forward()

如果想要同时取出多个,则将多个编号写在一个序列中即可,例如 typ['theta', (0, 1)] 则是取出编号为 01 的匹配项,在这里就是所有匹配到的 θ 符号。

你应该发现了,取出 (0, 1) 的项,其实就是取出所有项,对于这种情况,JAnim 提供了 typ['theta', ...] 的方式,使用省略号表示取出所有的匹配项。

一些特殊情况

当你想要取出

typ = TypstMath('cos^2 theta + sin^2 theta = 1')

中的上标 “2” 时,使用 typ['2'] 无法匹配到它,这是因为上标的 “2” 和普通的 “2” 长得不同。

为了正确匹配,你需要把索引中的 2 也表示为“上标”的形式,例如 typ['""^2'] 或者 typ['space^2'], 这两者都是把 “2” 作为一个空元素( "" 或者 space ) 的上标,这样就可以正确匹配了。

重要

上面以 TypstMath 作为字符索引的例子,TypstDocTypstText 也是几乎一致的,但是会有略微区别

我们知道,在这三种对象中,只有 TypstMath 是在公式环境中的,所以进行它的字符索引时,作为索引的字符串也会在公式环境中解析

这意味着,对于 TypstDocTypstText 而言,作为索引的字符串不在公式环境中

这里给出几段示例作为参考:

t = TypstMath('cos theta')
t['theta']

t = TypstText('$ cos theta $')
t['$theta$']
t = TypstText('this is a formula: $cos^2 x + sin^2 x = 1$')
t['formula']
t['$x$']

内置包

JAnim 提供了内置包可以在 Typst 中使用 #import 引入

  • #import "@janim/colors:0.0.0": *

    提供了 JAnim 中的颜色常量(可参考 颜色 条目),以便在 Typst 中使用

    点击展开 @janim/colors 的具体定义
    // Colors
    #let BLUE_E = rgb("#1C758A")
    #let BLUE_D = rgb("#29ABCA")
    #let BLUE_C = rgb("#58C4DD")
    #let BLUE_B = rgb("#9CDCEB")
    #let BLUE_A = rgb("#C7E9F1")
    #let TEAL_E = rgb("#49A88F")
    #let TEAL_D = rgb("#55C1A7")
    #let TEAL_C = rgb("#5CD0B3")
    #let TEAL_B = rgb("#76DDC0")
    #let TEAL_A = rgb("#ACEAD7")
    #let GREEN_E = rgb("#699C52")
    #let GREEN_D = rgb("#77B05D")
    #let GREEN_C = rgb("#83C167")
    #let GREEN_B = rgb("#A6CF8C")
    #let GREEN_A = rgb("#C9E2AE")
    #let YELLOW_E = rgb("#E8C11C")
    #let YELLOW_D = rgb("#F4D345")
    #let YELLOW_C = rgb("#FFFF00")
    #let YELLOW_B = rgb("#FFEA94")
    #let YELLOW_A = rgb("#FFF1B6")
    #let GOLD_E = rgb("#C78D46")
    #let GOLD_D = rgb("#E1A158")
    #let GOLD_C = rgb("#F0AC5F")
    #let GOLD_B = rgb("#F9B775")
    #let GOLD_A = rgb("#F7C797")
    #let RED_E = rgb("#CF5044")
    #let RED_D = rgb("#E65A4C")
    #let RED_C = rgb("#FC6255")
    #let RED_B = rgb("#FF8080")
    #let RED_A = rgb("#F7A1A3")
    #let MAROON_E = rgb("#94424F")
    #let MAROON_D = rgb("#A24D61")
    #let MAROON_C = rgb("#C55F73")
    #let MAROON_B = rgb("#EC92AB")
    #let MAROON_A = rgb("#ECABC1")
    #let PURPLE_E = rgb("#644172")
    #let PURPLE_D = rgb("#715582")
    #let PURPLE_C = rgb("#9A72AC")
    #let PURPLE_B = rgb("#B189C6")
    #let PURPLE_A = rgb("#CAA3E8")
    #let GREY_E = rgb("#222222")
    #let GREY_D = rgb("#444444")
    #let GREY_C = rgb("#888888")
    #let GREY_B = rgb("#BBBBBB")
    #let GREY_A = rgb("#DDDDDD")
    
    #let PURE_RED = rgb("#FF0000")
    #let PURE_GREEN = rgb("#00FF00")
    #let PURE_BLUE = rgb("#0000FF")
    
    #let WHITE = rgb("#FFFFFF")
    #let BLACK = rgb("#000000")
    #let GREY_BROWN = rgb("#736357")
    #let DARK_BROWN = rgb("#8B4513")
    #let LIGHT_BROWN = rgb("#CD853F")
    #let PINK = rgb("#D147BD")
    #let LIGHT_PINK = rgb("#DC75CD")
    #let GREEN_SCREEN = rgb("#00FF00")
    #let ORANGE = rgb("#FF862F")
    
    // Be compatible with the old names
    #let GREEN_SCREEN = rgb("#00FF00")
    
    // Abbreviated names for the "median" colors
    #let BLUE = BLUE_C
    #let TEAL = TEAL_C
    #let GREEN = GREEN_C
    #let YELLOW = YELLOW_C
    #let GOLD = GOLD_C
    #let RED = RED_C
    #let MAROON = MAROON_C
    #let PURPLE = PURPLE_C
    #let GREY = GREY_C
    

备注

如果你需要脱离 JAnim 在外部 .typ 文件中编写 Typst 代码,希望其也能引入 JAnim 的内置包

你需要将 <site-packages>/janim/items/svg 完整路径通过 --package-path 选项传递给 Typst 编译器或 Tinymist 插件的 "tinymist.typstExtraArgs" 选项

语法高亮

前面提到,如果你使用了 VSCode 插件 janim-toolbox, 会自动给 TypstDocTypstText 中出现的 Raw-字符串(形如 R'...') 进行 Typst 语法高亮。

对于同样需要 Typst 语法高亮,但不在 TypstDocTypstText 之中的字符串,你可以使用 t_ 函数来标注需要 Typst 高亮,例如

LightTyp = partial(TypstText, color=YELLOW)

typ = LightTyp(t_(R'#box(width: 10em)[#lorem(20)]')).show()
with Config(typst_shared_preamble='#set box(width: 3em, height: 3em)'):
    group = Group.from_iterable(
        TypstText(content) for content in t_(
            R'#box(stroke: red)',
            R'#box(fill: red)',
            R'#box(stroke: red, outset: 2pt)[ab]',
            R'#box(fill: aqua)[A]',
        )
    )

group.points.arrange()
group.show()

嵌入 JAnim 物件

Typst 物件支持传入 vars 参数嵌入 JAnim 物件:

typ1 = TypstText(
    R'This is a sentence with an inserted #star JAnim item',
    vars={
        'star': Star(outer_radius=0.5, color=YELLOW, fill_alpha=0.5)
    },
    vars_size_unit='em'
)

typ2 = TypstText(
    R'''
    #box(fill: luma(40%), inset: 8pt)[
        This is a grid containing JAnim items
        #grid(
            columns: 2,
            fill: luma(20%),
            gutter: 4pt,
            inset: 8pt,

            [$f(x)$\ math content],
            gif,
            star,
            [QwQ\ text content]
        )
    ]
    ''',
    vars={
        'gif': Video('Ayana.gif', loop=True).start(),
        'star': Star(),
    },
)

Group(typ1, typ2).show().points.arrange(DOWN)
self.forward(4)

参考:

TypstText Video

提示

未传入 vars_size_unit 时,嵌入的 JAnim 物件会保留在 JAnim 中原有的大小,若传入了 vars_size_unit 则将其大小乘上对应的单位。

例如对于一个在 JAnim 中高度为 1 的物件,如果直接插入 Typst 文字中会显得很大,此时设置 vars_size_unit='em' 使其插入高度变为 1em,则基本与文字高度匹配。

vars 是一个字典,它的键会作为 Typst 代码中可以使用的变量名,值会作为变量名对应的 JAnim 物件,并且支持进一步嵌套列表和字典:

TypstText(
    ...,
    vars=dict(
        shapes=[
            Star(),
            Square(),
            Circle()
        ],
        mapping=dict(
            txt=Text('This is a JAnim sentence'),
            vid=Video('example.mp4').start()
        )
    )
)

备注

在 Typst 中嵌入 JAnim 物件,从原理上来讲是创建了一个对应大小的占位 box,然后在 Typst 物件创建后,将其替换为 JAnim 物件,从而做到嵌入的目的。

特殊类型的 Typst 物件

参考文档