一直以来文字排版都是一项复杂的工作。计算机出现不久后,人们就尝试用计算机取代铅字处理排版工作。现在计算机上的排版工具有很多。Microsoft Office Word 可能是使用最广泛的排版工具。它容易上手,功能丰富,能够满足绝大多数办公场景。缺点是文件格式私有,价格昂贵;面对一些复杂排版需求(如公式排版)力不从心。随着 web 技术的发展,HTML + CSS 也可以作为排版工具;Markdown 这种可以编译成 HTML 的简易标记语言广泛应用于网络文字排版(本站的文章都是用 Markdown 写的)。但是 web 技术主要服务于网页设计,缺失换行、分页算法,也难以应对复杂排版。由著名计算机科学家、图灵奖获得者 Donald E. Knuth 发明的 $\mathrm{\TeX}$ 及其衍生品 $\mathrm{\LaTeX}$ 因其强大的功能、美观的排版效果、优秀的换行分页算法、出色的公式排版能力、灵活的可定制性,一直以来是排版系统的黄金标准。但是由于年代久远,上手难度高、历史包袱众多。那么有没有什么功能强大、容易上手、开源免费的排版系统推荐一下呢?有的兄弟,有的。今天我要推荐的 Typst 就是这样一款优秀的排版工具。
来自柏林工业大学的计算机研究生 Martin Haug 和 Laurenz Mädje 不满足于 $\mathrm{\TeX}$ 的繁琐和臃肿,决定开发一款新的排版工具,这便是 Typst。Typst 功能强大、效果出色,使用却非常简单,容易上手。Typst 是文本文件格式,后缀通常是 .typ
。
= 这是标题
== 这是二级标题
为了展示Typst上手有多么简单,我们直接展示一段Typst代码。Typst的标记语法类似于Markdown:这是行内代码 `inline code`,这是*加粗*,这是斜体_italic_,这是公式 $e^(pi i) + 1 = 0$。
用空行表示分段。这是无序列表:
- 无序列表
- 用横杠 `-` 标记无序列表
- 多层列表
- 缩进表示多层列表
这是有序列表
+ 有序列表
+ 用加号 `+` 标记有序列表
- 同样支持多层
```c
int main() {
printf("三个反引号表示代码块,支持代码高亮\n");
return 0;
}
```
Typst还是一门强大的编程语言。可以调用函数实现各种样式:#text(size: 15pt, fill: red)[调用`text`函数插入自定义样式的文本。]还有#highlight(fill: yellow)[`highlight`高亮]、#underline(stroke: 1.5pt + red)[`underline`下划线]、#strike[`strike`删除线]等。实际上Typst的标记语法大多数都是函数的简便写法。例如#strong[`strong`加粗]、斜体#emph[italic]
#heading(depth: 3)[这是三级标题]
等价于 `=== 这是三级标题`。通过自定义函数,可以实现复杂的自定义效果。
#let myStyle(content) = { // 定义函数
let styled = text(size: 1.3em, fill: yellow, stroke: 0.3pt + red, font: "KaiTi", content)
return underline(stroke: 1.2pt + blue, styled)
}
这就是#myStyle[调用自定义函数的效果]。
{class="shadow"}
Typst 的安装非常简单。如果你用 vscode,那么最简单的方法就是安装 Tinymist 插件。它是一个非常棒的 Typst 写作环境,能实现实时预览。
也可以在官网下载下载软件本体。Typst 只有一个可执行文件,执行 typst compile
即可将 Typst 源文件编译成 pdf。
$ typst compile test.typ
Typst 有三种模式:标记模式、数学模式和代码模式。默认为标记模式,使用类似 Markdown 的语法写作文本。数学模式用于编排数学公式;代码模式则用于实现各种可编程功能。这三种模式之间可以互相切换:
#
号切换到代码模式。#
后紧跟代码,直到整个语句结束都是代码模式。如果有歧义,可使用分号 ;
标记语句结束。[...]
切换到标记模式。例如前面看到的 #strong[`strong`加粗]
,方括号内便可使用标记语法;这个语句将标记文本传入 strong
函数获得加粗效果。$...$
切换到数学模式。如上面看到的,标记模式的语法与 Markdown 相似。基本上每个标记语法有对应的函数,后面我们介绍函数的用法。
语法 | 含义 | 元素函数 |
---|---|---|
= 标题 |
等号的数量表示标题的层级 | heading |
*加粗* |
加粗字体 | strong |
_强调_ |
斜体强调。中文字体一般没有斜体,所以一般不生效。 | emph |
- 无序列表 |
无序列表 | list |
+ 有序列表 |
有序列表 | enum |
空行 | 分段 | parbreak |
`code` |
代码(是否为行内代码取决于是否分行写)。使用三个反引号可支持高亮:```c return 0;``` |
raw |
$y=k x + b$ |
数学公式(是否为行内公式取决于是否分行写) | 数学模式不是函数 |
更多语法见官方文档。
Typst 的数学语法不同于 LaTeX,但比它简单。Typst 中单个字母表示它本身;但多个字母表示特殊值或函数,类似于 LaTeX 省略反斜杠。如果要表示多个字母本身,就需要加双引号。
与 LaTeX 用花括号 {}
不同,Typst 中函数参数放在小括号里面,不同的参数用逗号 ,
分隔。上下标的用法与 LaTeX 一致,^
表示上标,_
表示下标。此外 Typst 的数学公式有很多简便用法。例如 $\ne$ 可以写作 !=
,$\le$ 可以写作 <=
,分数 \frac{a}{b}
可以用斜杠 /
等。
详细用法见 https://typst.app/docs/reference/math/
Typst 是完善的编程语言,有很多通用编程语言的特性。
#let factorial(x) = { // let 定义函数
let i = 1; // let 定义变量
let ans = 1;
while i <= x { // while 循环
ans *= i;
i += 1;
}
return ans; // 返回结果
}
#let a = 10; // 定义变量
#a;的阶乘等于#factorial(a) // 调用函数
{class="shadow"}
下面展示了一些常用语法:
语法 | 含义 |
---|---|
let a = 1 |
定义变量 |
let f(x, y) = { return x * y; } |
定义函数 |
let f(x, y) = x + y |
定义函数。如果没有 return 语句,函数的返回值等于函数体所有表达式的拼接 |
let f(x: 0, y: 0) = x + y |
带命名参数 (named argument)的函数定义。命名参数自带默认值,调用时是可选的 |
(x, y) => x + y |
匿名函数表达式 |
42 , 0xff |
整数 |
3.14 , 1e10 |
浮点数 |
"hello" |
字符串 |
10pt , 1.5em |
长度 |
90deg , 1rad |
角度 |
50% |
比例 |
1fr |
分数 |
(1, 2, 3) |
数组 |
(a: 1, b: "ok") |
字典 |
a = 1 |
赋值 |
-a , a + b |
一元运算符和二元运算符 |
f(a, b) |
调用函数 |
enum([a], [b]) |
调用 enum 函数,传入两个类型为标记内容 (content) 的参数([...] 切换到标记模式) |
enum(start: 2, [a], [b]) |
带命名参数的函数调用 |
enum(start: 2)[a][b] |
语法糖,与上面的写法等价。标记内容参数可以放在括号外面 |
x.y |
成员访问 |
x.flatten() |
方法调用 |
完整的语法见 https://typst.app/docs/reference/scripting/ 和 https://typst.app/docs/reference/foundations/
Typst 提供了很多用于实现各种样式的函数,例如文字样式、段落、图表、表格、列表等等,称为元素函数 (element function)。Typst 的标记语法基本上都是元素函数的简便写法。例如用于创建各种样式的文本的 text
函数,它有很多参数。下面列举了它的一小部分参数:
text(
font: str | array,
weight: int | str,
size: length,
fill: color,
stroke: none | length | color,
tracking: length,
spacing: relative,
lang: str,
str,
content,
) -> content
font
字体weight
字重。从细到粗分别是 "thin"
, "extralight"
, "light"
, "regular"
, "medium"
, "semibold"
, "bold"
, "extrabold"
, "black"
size
字体大小。如 10pt
, 1.5em
fill
填充色。如 red
, rgb("#eb27ba")
stroke
描边,可以是长度 + 颜色。如 0.3pt + red
表示 0.3 像素红色描边tracking
字母间距spacing
单词间距lang
语言str
字符串文本content
也可传入标记内容利用 text
函数我们就能生成各种各样的文本了。
完整的元素函数可参考 https://typst.app/docs/reference/model/。下面列出了一些例子:
如果每次使用自定义样式时都要显式调用元素函数,未免有些太麻烦了。Typst 提供了两种语法用于定制样式:set 规则和 show 规则。
Set 规则很容易理解:在文档中显示任何元素都实际上是调用元素函数的结果,那么 set 语句就是用于设置元素函数某些参数的默认值。例如 #set text(size: 20pt)
,那么所有文字的大小都会变成 20 点。
Set 规则作用范围是当前 block。所谓 block 就是内容块 [...]
或代码块 {...}
。例如
#set text(fill: red) // 全局生效
#[
#set text(size: 20pt) // 当前 block 生效
红色 20 点文字
]
红色默认大小文字
#let bold(content) = {
set text(weight: "bold") // 当前函数的 block 生效
content
}
#bold[加粗字体]
正常字体
Set 规则非常实用。有些元素函数,例如 par
(段落), page
(页面), document
(文档),我们很少直接调用它们,而是将它们应用 set 规则,设置它们的样式。
Show 规则的一种写法是关键词 show
+ 选择器 + :
+ set 语句,表示将所有满足选择器的元素应用指定的 set 规则。最常用的选择器就是元素函数,例如下面的语句表示将所有的标题文字设置为海军蓝:
#show heading: set text(fill: navy)
另一种写法是 show
+ 选择器 + :
+ 函数,表示将所有满足选择器的元素传入指定函数。例如下面的语句表示将所有的超链接的样式设置为带下划线的蓝色文字:
#show link: (a) => underline(text(fill: blue, a))
选择器除了是元素函数外,还可以是以下几种:
show "Text": ...
所有匹配到指定字符串的内容应用指定样式。这在某些场景非常实用。
show regex("\w+"): ...
所有匹配到指定正则表达式的内容应用指定样式。show heading.where(level: 1): ...
元素函数支持 .where
方法,返回一个选择器,只选择指定参数的元素。这个例子将所有 1 级标题应用指定样式show: ...
所有内容应用指定样式。如果 :
后面是函数,就会把整篇文档传入函数,这在模板的使用中非常常用。详细用法见 https://typst.app/docs/reference/styling/
Typst 很适合制作技术简历。在 Typst Universe 中,有很多简历模板。我们从中挑选一个,例如 basic resume。我们执行
typst init @preview/basic-resume:0.2.8
初始化一个 typst 工程。使用 vscode 打开 basic-resume/main.typ
便可以开始编辑了。
首先使用 import
语句引入 basic-resume 模块的内容。这里的 *
表示引入模块中的所有符号。
#import "@preview/basic-resume:0.2.8": *
接着用 let
定义一些可能会复用的变量。接下来是最关键的 show 语句:
#show: resume.with(
author: name,
location: location,
email: email,
...
)
resume
是 basic-resume 模块中定义的一个函数。我们可以看到它的定义:
#let resume(
author: "",
location: "",
email: "",
...
body,
) = {
...
}
它本质上是一个有很多命名参数和一个普通参数 body
的函数,将内容 body
转换成一篇简历并返回。而 with
实际上是函数对象的一个方法,它返回一个预应用了给定参数的新函数。例如函数 let f(a, b) = a + b
,f.with(1)
就等价于 (b) => f(1, b)
。那么这里 resume.with(...)
就得到一个各种命名参数设置好了的新函数。这里的 show 语句会将整个文档作为参数传入这个新函数,我们就能得到一篇简历了。
接下来就是简历正文,也就是被传入 resume
函数的内容。其中的各种语法我们已经基本上已经介绍过了,这里无非是调用模板中定义的函数插入各种内容。例如 #edu(...)
插入教育经历、#work(...)
插入工作经历、#project(...)
插入项目经历等。
编辑完成后,点击 "Export PDF",或者手动执行 typst compile main.typ
就可以得到 PDF 格式的简历了。
过去我总觉得各种排版系统都不是特别好:Word 排版效果一般,且不便版本控制;Markdown 排版能力弱;LaTeX 古老且使用复杂,编译缓慢。直到发现了 Typst,使用过后立刻就喜欢上了。本文只是简单推荐,而不是详细的教程。如果要深入学习,可参考 Typst 官方文档 https://typst.app/docs,或者直接咨询 AI。