个人的自用模板代码可以查看 Typst-Snippets

Typst 是 2019 年才出现的使用 Rust 编写的基于标记的排版语言,定位在 Markdown、Word 等初级工具和 LaTeX\LaTeX 一类的高级工具之间,官方宣称其功能可以和 LaTeX\LaTeX 一样强大,但是和 Markdown 一样简单、易用,主要应用于数学、物理和工程方面(特别是包含大量公式、图表)的论文、文章、作业、书籍和报告的编写。

基础教程

MacOS 和 Arch Linux 用户可以使用包管理器进行安装:

1
2
3
4
5
# macOS or Linux using Homebrew
brew install typst

# Arch Linux
pacman -S typst

具体支持的发行版本可以查看 参考资料 (5)。Ubuntu 目前还只能下载编译好的二进制文件,或者安装 Rust 后从源码编译进行安装。除此之外,还需要安装编辑器的插件实现代码提示、预览等功能,VS Code 上的主要插件有:

除此之外,还有 Typst Sympy Calculator 插件实现了和 Python 的 SymPy 库的联动,可以进行公式的转化和计算。

对于从 LaTeX\LaTeX 和 Markdown 迁移过来的用户,绝大部分语法内容都可以新学习,比较重要的区别有:

  1. 公式中命令不需要 \ 开头,直接使用即可,例如使用 $e^(pi eta)$ 显示 eπηe^{\pi\eta},要在公式中显示成段的,不是命令的字符,可以使用 " 包裹起来。更具体而言,Typst 区分两种模式:「标记模式」和「代码模式」,前者是正常的文本模式,类似于 Markdown 的编写逻辑,后者以 # 开头,在该模式内的命令省略 #(个人认为,公式是一种特殊的代码模式,命令在公式中也可以省略 #
  2. 函数定义要求函数名 + 括号,参数可以是位置参数或者命名参数,格式为 myfunc(keya: valuea, keyb: valueb)
  3. Typst 的代码模式十分强大,通过 #if 条件判断和 #for 循环语句,可以实现 参考资料 (7) 中的效果:

具体的内容可以查看 参考资料 (3),入门的教程可以查看 参考资料 (2),一些 Typst、Markdown 和 LaTeX\LaTeX 之间的对比可以查看 参考资料 (7)

常用代码

官方模组

目前官方收录的模组可以在 Typst Packages 进行查询,此外还有 Awesome Typst 项目收录了很多实用了模组,下面列举了部分常用的模组,一些我进行了自定义的模组将会在 (下一小节) 进行介绍。

  1. Pinit 包用于创建批注,在制作 slides 的时候特别有用,效果如下:
Pinit
Pinit
  1. Physics 实现了向量、场、微分、导数、狄拉克符号等物理中常用符合的定义:
Physics
Physics
  1. Showybox 可以创建自定义的彩色盒,可以用于定义、定理等场景:
Showybox
Showybox
  1. Tablex 实现了高级的表格:
Tablex
Tablex
  1. Typst orange template 使用 Typst 实现了 Legrand Orange Book
Typst orange template
Typst orange template
  1. i-figured 实现了图表、公式的按章节编号,参见 (论文)

自定义模板

幻灯片

Awesome Typst 中推荐的幻灯片模板中,个人推荐使用 Polylux 模板,提供了多种主题,目前我常用的是 [University] 主题,并进行了一定的修改。

封面
封面
目录
目录
分栏
分栏
  • 给分栏页加入页眉和页脚
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#let matrix-slide(
  title: none,
  new-section: none,
  columns: none,
  rows: none,
  ..bodies
) = {
  let header = {
    set align(top)
    grid(rows: (auto, auto), row-gutter: 3mm, progress-barline, header-text(title: title, new-section: new-section))
  }

  let footer = {
    set text(size: 10pt)
    set align(center + bottom)
    let cell(fill: none, it) = rect(
      width: 100%, height: 100%, inset: 1mm, outset: 0mm, fill: fill, stroke: none,
      align(horizon, text(fill: white, it))
    )
    locate( loc => {
      let colors = uni.uni-colors.at(loc)

      show: block.with(width: 100%, height: auto, fill: colors.b)
      grid(
        columns: (25%, 1fr, 15%, 10%),
        rows: (1.5em, auto),
        cell(fill: colors.a, uni.uni-short-author.display()),
        cell(uni.uni-short-title.display()),
        cell(uni.uni-short-date.display()),
        cell(logic.logical-slide.display() + [~/~] + utils.last-slide-number)
      )
    })
  }

  set page(
    margin: ( top: 2em, bottom: 1em, x: 0em ),
    header: header,
    footer: footer,
    footer-descent: 0em,
    header-ascent: .6em,
  )

  uni.matrix-slide(..bodies)
}
  • 添加目录页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#let slide-outline-length(loc) = {
  let section-state = state("polylux-sections", ())
  let sections = section-state.final(loc)
  sections.len() - 1
}

#let slide-outline(num) = {
  set text(font: "Cascadia Mono")
  locate(loc => {
    let section-state = state("polylux-sections", ())
    let sections = section-state.final(loc)
    for p in range(0, num) {
      let section = sections.at(p)
      let content = {
        let url = link(section.loc, section.body)
        let page = logic.logical-slide.at(section.loc).at(0)
        let length = 46 - str(page).len() - count-length(section.body)
        let dots = " " + "." * length
        [#grid(
          columns: (auto, 1fr, 1fr),
          url, align[#dots], align(right)[#page]
        )]
      }
      [+ #content ]
    }
  })
}

论文

LaPreprint
LaPreprint

LaPreprint 是一个很美观的预印本模板,主要适用于英文,为了适应一些中文的需求,个人做出了一些修改:

  • 增加目录与语言选项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#let lapreprint(
  // ......
  // The outline
  outline-show: true,
  outline-depth: 2,
  // Language of the document
  lang: "en",
  // The paper's content.
  body
) = {
  // ......
  if (outline-show) {outline(
    title: if (lang == "en") {"Content"} else {"目录"},
    indent: 2em,
    depth: outline-depth,
  )}
  // ......
}
  • 修改页边距
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 首页 & 目录页
#lapreprint(
  set page(
    paper-size,
    margin: (left: 20%, bottom: 1.5cm),
  )
)

// 后续页
#lapreprint(
  set page(
    paper-size,
    margin: (left: auto, bottom: 1.5cm),
  )
)
  • 中文显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#lapreprint(
  set page(
    // ......
    footer: block(
      // ......
                if(date != none) {
                  if (lang == "en") {
                    date.display("[month repr:short] [day], [year]")
                  } else {
                    [#date.year()#date.month()#date.day()]
                  }
                }
      // ......
    )
  )

  // ......
  place(
    // ......
              if (lang == "en") {
                text(size: 7pt, d.date.display("[month repr:short] [day], [year]"))
              } else {[
                #set text(7pt)
                #d.date.year()-#d.date.month()-#d.date.day()
              ]}
    // ......
  )

  // ......
  if abstracts != none {
    // ......
        if (lang == "en") {
          text(fill: theme, weight: "semibold", "Keywords")
        } else {
          text(fill: theme, weight: "semibold", "关键词")
        }
    // ......
  }
)
  • 图表、公式编号,按章节区分
1
2
3
4
5
6
7
8
#lapreprint(
  // Configure i-figured
  show figure: i-figured.show-figure
  show figure.where(
    kind: table
  ): set figure.caption(position: top)
  show math.equation: i-figured.show-equation
)
  • 交叉引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#lapreprint(
  // Configure Reference Link
  show link: it => [#text(fill: theme)[#it]]
  show ref: it => [#text(fill: theme)[#it]]
  show ref: it => {
    let el = it.element
    if (el != none) {
      let fn = repr(el.func())
      if (fn == "equation") {link(
        it.target,
        if (lang == "en") {"Eq."} else {"式 "} + 
        numbering(
          el.numbering,
          ..counter(math.equation).at(el.location())
        )
      )} else if (fn == "heading") {link(
        it.target,
        if (lang == "en") {"Section "} else {"第 "} + 
        numbering(
          el.numbering,
          ..counter(heading).at(el.location())
        ) + if (lang == "zh") {" 节"}
      )}
      else { it }
    } else {it}
  }
)

其他

  1. 借助 Tablex 可以实现三线表的样式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 定义
#import "@preview/tablex:0.0.6": tablex, rowspanx, colspanx, hlinex, vlinex

#let three-line-table(columns, headers, contents, caption, lang: "en",rows: auto) = {figure(
  tablex(
    columns: columns,
    rows: rows,
    align: center + horizon,
    auto-lines: false,
    repeat-header: true,
    hlinex(stroke: 1.5pt),
    ..headers,
    hlinex(stroke: 0.75pt),
    ..contents,
    hlinex(stroke: 1.5pt),
  ),
  kind: table,
  supplement: [#if (lang == "en") {"Table"} else {"表"}],
  caption: caption,
)}

// 使用
#three-line-table(
  2,
  ([*参数*], [*结果*]),
  (
    [$sin^2(2 theta_(12))$], [$0.857 pm 0.024$],
    [$sin^2(2 theta_(23))$], [$> 0.95$],
    [$sin^2(2 theta_(13))$], [$0.098 pm 0.013$],
    [$Delta\m_(12)^2$], [$(7.50 pm 0.20) times 10^(-5) "eV"^2$],
    [$Delta\m_(23)^2$], [$0.00232_(-0.00008)^(+0.00012) "eV"^2$],
    [$delta\/pi$], [$1.35_(-0.43)^(+0.64)$]
  ),
  [中微子振荡参数测量结果#super[@zqw2015daya @zym2018daya]],
  lang: "zh",
) <nu_para>
三线表
三线表
  1. 公式块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#let box-quote(body, width: 100%, inset: 5pt, outset: 0pt) = {block(
  fill: green.lighten(80%),
  width: width,
  inset: inset,
  outset: outset,
  radius: 4pt,
  stroke: green.darken(60%),
  breakable: true,
  body
)}

// 使用
#box-quote[$
P_(nu_alpha -> nu_beta) approx sin^(2)2theta sin^(2)((m_1^2-m_2^2)/(4p)t) approx sin^(2)2theta sin^(2)(Delta\m^2 L/(4E))
$ <2flavor_mix_4>]
公式块
公式块
  1. 数学符号的简易别名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "@preview/physica:0.9.0" as ph
#let sp = [#h(0.5em)]
#let pm = [#sym.plus.minus]
#let le = [#sym.lt.eq.slant]
#let ge = [#sym.gt.eq.slant]
#let sim = [#sym.tilde.op]
#let inf = [#sym.infinity]
#let rm(body) = {math.upright(body)}
#let iso(b, A) = [#ph.isotope(b, a: A)]
#let isoz(b, A, Z) = [#ph.isotope(b, a: A, z: Z)]

// 使用
#box-quote[$
1 pm 2 le 3 ge 4 sim 5 inf 6 sp rm(A\b) sp 7 sp "Ab" 8 sp iso(U, 235) sp 9 sp isoz(L\i, 7, 3)
$]
数学符号
数学符号

参考资料

  1. Typst
  2. Documentation
  3. 为LaTeX用户准备的Typst入门
  4. Typst Tablex 简单教程
  5. Typst on Repology
  6. 中文用户指南
  7. Typst-"超越"LaTeX的文档排版工具
  8. Typst Examples Book