Image Image Image Image Image

07

12 月

Typst:一种轻量化排版语言

  • By IanGoo

接触到 Typst 完全是偶然,因为在知乎上闲逛的时候看到了一个关于 Typst 的问题,问它能在多大程度上取代 Microsoft Word。作为日常码字爱好者,我以为这是某种新的字处理软件,就去查了一下,这一查就跳坑了。

在接触 Typst 之前,我是 HTML 熟练工、Markdown 深度用户、\LaTeX 浅度用户(一边查文档一边磕磕巴巴能用的水平),对于“标记语言”的理念还是比较深入的。Markdown 也是我日常码字的常用语言,但是 Markdown 一直有一个让我比较难受的特点:这玩意儿的终点是 HTML,如果想要用它输出打印稿,只能先将 Markdown 转成 HTML,然后在浏览器里打印为 PDF——而这个过程是完全不可控的,整个 Workflow 也有一种脱了裤子放屁的优雅感。这也是我顶着满屏幕的 \ 嗯学 \LaTeX 的主要原因。

\LaTeX 是一个很伟大的发明,也是目前地球上最强大的排版工具。但是它也有一堆自己的问题。首先就是效率。我学 \LaTeX 用的是 Overleaf,这是目前看来最简单、最易用的 \LaTeX 工具,但是只要文档稍微一复杂,内容稍微一多,编译就需要非常漫长的时间,而 Overleaf 卡免费帐户的手段就是限制编译时间。果然一个工具的优劣势还是靠这个工具吃饭的人掌握得最清楚。然后就是它的语法,前面说了,满屏幕都是 \ 看着眼睛疼。

而 Typst,在稍微接触之后,我便意识到了它的定位,相当有趣:Typst 之于 \LaTeX,就相当于 Markdown 之于 HTML。

有人不喜欢手撸 HTML,因此有人用一系列标记替代 HTML 标签,这就有了 Markdown。同理,有人不喜欢手撸 \LaTeX,因此也有人用一系列标记替代了大量 \LaTeX 命令,Typst 大致就是这么一个定位。

当然,HTML 和 \LaTeX 不是一个量级的东西,后者是一个图灵完备的程序语言,因此简化后的 Typst 还是比 Markdown 复杂不少的。下面就是对 Typst 的一些简单的介绍。

部署

学习一个软件必然要伴随着实践。否则根本就不可能学得会。我在学 \LaTeX 的时候选择了 Overleaf 的主要原因之一就是 \LaTeX 的本地部署太蛋疼了。但是 Overleaf 的渲染时间刀法又过于精准,所以这就让 \LaTeX 的学习过程一直处于一种有一搭没一搭的状态。

而 Typspt,在软件部署这一点就大赢特赢,赢麻了——我现在使用了两种 Typst 的实现,都非常简单而且体验顶级。

第一种:直接使用官方提供的 Web App。只需要登陆官方网站,注册一个账号,就可以使用了。

Typst Web App 的布局和 Overleaf 非常相似,左边是源码,右边是渲染预览。但是区别在于 Typst 的效率高太多了,基本上就是实时预览,我在左边打字,都不等拼音输完,右边就会刷新显示。这一点就比 Overleaf 高太多了。

当然天下没有免费的午餐,Typst Web App 的免费账号有一定限制,不过限制就很传统了:200 MB 空间限制。因为 Typst 的渲染速度非常快,完全不用限制免费账号的渲染时间。

第二种:无所不能的 VSCode 安装 Tinymist 插件,然后将 .typ 文件关联一下就 OK 了。

VSCode + Tinymist 方案是一个完全本地化的方案。看到“本地化”三个字顿感非常亲切。因为在当前的技术条件下,本地方案就是会比云服务更快,毫无疑问。虽然 Typst 本质上是在执行纯文本操作,但是它还是会调用大量素材资源,比如图片、字体、外部引用的代码等等。

所以本地会比云端的 Web App 更快?恰恰相反,测试表明 Tinymist 由于有一些额外的通讯开销,性能反而是更低的。不过由于总体上 Typst 的运行效率相当高,这两者之间的差异并不能明显地感知出来。反而是由于本地部署的 Tinymist 更加灵活、可以充分调用本地的资源,我更推荐使用 VSCode + Tinymist 这个组合来写 Typst。

除了这两个方案之外,Vim、Emacs 等老牌的编辑器在社区的共同努力下也开始具备了不错的 Typst 编辑体验。最后一种方案就是官方的基础款解决方案:typst-cli。这一软件可以在官方的 Repository 中的 Release 页面下载。和很多 cli 应用一样,只需要解压后添加路径到 PATH 环境变量,就可以在命令提示行里使用了。语法也很简单:

typst compile xxx.typ yyy.pdf

便可以将 xxx.typ 编译为 yyy.pdf。不过,这种简单的导出 PDF 的功能在 Web App 和 Tinymist 当中都可以轻松实现,所以也就没有必要专门去下载 typst-cli 来使用了。

模式

\LaTeX 的设计思路是:遇到 \\{} 就将其视为命令进行解析,否则就将其是为文档的一部分。Typst 的设计思路和它非常相似。

Typst 有三种工作模式:

  1. 标记模式(Markup Mode)
  2. 代码模式(Code Mode)
  3. 数学模式(Math Mode)

默认情况下,也就是打开了一个完全空白的 .typ 文件的时候,Typst 将工作在标记模式下。这时候的 Typst 的工作方式和 Markdown 极为相似。除了两点:第一、Typst 作为排版语言,它会以默认的文档设置在纸张上显示预览;第二、Typst 的标记语法和 Markdown 有所不同,但是也相当好学。

一张表就可以解释 Typst 标记模式里的一切:

名称代码样例对应函数
新段落(空一行)#parbreak()
加粗*Strong*#strong[Strong]
斜体_Italic_#emph[Italic]
源码print(1)#raw("print(1)")
超链接直接写URL#link("URL")
书签<label>#label("label")
引用@label#ref(<label>)
标题== Heading 2#heading("Heading 2", level: 2)
无序列表– Item 1#list("Item 1")
有序列表+ Item 1#enum("Item 1")
术语/ Term: 解释
换行\ #linebrak()
注释// 或者 /*..*/

可以看到,Typst 的标记和 Markdown 有一定的相似之处,比如一对下划线都是用来标记斜体强调,当然,更多的还是不同的,比如标题,Markdown 用的是井号而 Typst 用的是等号。不过 Typst 的标记已经非常简洁了,如果对 Markdown 有一定了解的话,学习 Typst 并不会有多困难。

第二种模式就是代码模式。这个也很好理解,Markdown 里也是允许直接手撸 HTML 代码的。比如在 HTML 里的 <strong>Strong</strong> 在 Markdown 里就写作 **Strong**,在 Typst 里,#strong[Strong] 也和 *Strong* 效果相同(虽然哲学层面上不一样)。

所以,将 Typst 和 \LaTeX 对比,有一个很重要的区别就是 Typst 将一些常见的控制命令改写成了更加简洁的标记。这恰如 HTML 向 Markdown 的变化。——但是需要说明,将 Typst 的代码模式理解为 HTML 的标签是不严谨的,后面会说。

最后一种模式就是数学模式,和 \LaTeX 以及 Markdown 很相似的是,数学模式的切换标记是美元符号。不过区别在于,Markdown 是使用 {equation} 的范式输入行内公式,使用

    \[{equation}\]

的范式输入公式块,而 Typst 的语法分别是 {equation}{equation}——对头,就是里面有没有空格的区别。

Typst 的数学语法和 \LaTeX 也很相似。比如牛顿-寇次公式:

    \[\int_a^b f(x) \mathrm {d} x \approx \sum_{i=0}^n w_i f(x_i)\]

\LaTeX 当中的代码是:\\int_a^b f(x) \\mathrm {d} x \\approx \\sum_{i=0}^n w_i f(x_i)。同样的公式在 Typst 中的代码是:integral_a^b f(x) upright(d) x approx sum_(i=0)^n w_i f(x_i)。你就说这俩像不像吧……事实上,如果能够较为熟练地使用 \LaTeX 输入数理公式的话,使用 Typst 敲公式几乎可以不用查手册——多数 Typst 的数学控制函数都是对应的英语自然原文。比如积分符号,在 \LaTeX 当中就是 \\int,而在 Typst 就是“积分”的英语:integral。简单直白。

但是也不得不说,它的功能和 \LaTeX 相比还有一些差距。规范性上也显得有些草台班子。

比如,在很多数理公式当中都会用到的加减号:±,在 \LaTeX 当中的命令是 \\pm。但是在 Typst 当中,两种方法:你可以直接在里面插入这个符号——对头,在 \LaTeXa \\pm b 在 Typst 当中直接就可以写 a ± b——别问我这个符号怎么输入,用中文输入法就可以。也可以直接插入 Unicode 转义 \\u{00b1}。唯独原生的写法比较蛋疼:#sym.plus.minus——是的,这是个函数。类似的还有约化普朗克常数 \hbar,在 \LaTeX 中就是一个 \\hbar 搞定,而在 Typst 当中则是 planck.reduce。自然,但是麻烦。这也是我在使用 Typst 写公式的时候经常卡壳的地方。

而在某些语句的解析上,我是不太喜欢的。Typst 在数学模式当中也带上了太多的“标记”思维。很典型的就是分数的解析。在 \LaTeX 当中,正斜杠就会被解析为字面意义上的正斜杠,a / b 就会被解析成 a / b,很字面意义。但是在 Typst 当中,正斜杠会被强制解析为分数,等效于插入 frac 函数。所以 a / b 会被解析成 \frac a b。想要输入单行分数反而比较麻烦,需要用转义,写成 a \\/ b 才行。这就比较蛋疼了。虽然看起来 a/b 这种写法比较随意,但是在某些地方也是需要用的。这也折射出 Typst 的设计思路在“标记”这一侧有些着墨过多、过度倾斜了。该正规的地方不那么正规。在这方面,Markdown 的多数方言实现脑子比较清晰——我 Markdown 设计出来就不是让你敲论文的,数理公式就直接照抄 \LaTeX 的就行了,但是 Typst 还是自己发明了一遍轮子,其实我个人觉得没必要。数学既然已经设计成了一个单独的模式,那完全可以像 Markdown 那样直接用现成的,也能让学习者减轻一些学习成本,脑子里少记一套规范。毕竟能学上 Typst 的多多少少都会 \LaTeX

写到这里,似乎会让人产生一种感觉:Typst 应该是某种“强化版”的 Markdown,遇到特殊标记的内容就按照对应的规范来解析就行了。

这种认识是不对的。还记得前面说过的一句话吗?

将 Typst 的代码模式理解为 HTML 的标签是不严谨的。

Typst 不是强化版 Markdown,而是发展中的 \TeX 平替。须知:

Typst 是一个专注于排版,但同时也是图灵完备的编程语言。

在 Typst 官网可以看到一些逆出天际的 Demo,比如——用 Typst 写的俄罗斯方块。所以,如何理解下面这段 Typst 代码?

将 Typst 的代码模式理解为 HTML 的标签是#strong[不严谨]的。

比较浅层次的理解,是将里面的 #strong[] 等效替代为 HTML 的 <strong> 标签,也就是要求解析器以粗体渲染“不严谨”这三个字。

但是实际上并非如此,这里的 # 的作用并不是标记某种标签,而是切换解析器的工作模式到代码模式,那么后面的 strong 其实也不是格式标签,而是一个函数。这个函数又有两种输出完全等效的写法:#strong[Strong]#strong("Strong")。这两者的写法也是不同的:前者的意思是使用 strong 函数处理方括号中的标记文本;后者的意思是将字符串 Strong 作为参数传递给 strong 函数,并且输出结果。

听起来很绕,但是这就涉及到 Typst 中的模式切换了。Typst 的默认模式为标记模式,到 # 切换成代码模式、到美刀切换成数学模式、到 [] 切换成标记模式。所以在 #strong[Strong] 这种写法当中,方括号中的文本是标记文本,而 #strong("Strong") 中的 Strong 则是字符串参数。虽然二者的输出结果相同,但是处理思路是不一样的。

那么,为什么要设计成这种听起来有些叠床架屋的范式?那么请看这么一段话:

3 的 4 次方计算结果为 #calc.pow(3,4)。

解析出来是:

3 的 4 次方的计算结果为 81。

在这段代码中,我在 # 后面引用了一个函数 calc.pow,这是用于幂次计算的函数,然后就在一行文字里面,它就这么水灵灵地把答案给我算出来了。

所以,什么叫 Literate Programming 啊?Typst 的文本和代码的无缝衔接程度甚至能让 Jupyter 汗颜——Jupyter 的 MD 段和代码段可得用段落分隔的。

当然,作为排版语言,和 \LaTeX 一样,Typst 有大量的函数都是用于控制页面的版式的。也有一些版面功能是只提供了函数入口而没有提供标记语法的,比如表格——类比一下就是在 Markdown 里面只能用 <table> 标签敲表格而不能用 |-| 标记语法。这就是 Typst 重要的本职工作了——

排版

如果熟悉 \LaTeX 的话,上手 Typst 应该是毫无压力的。比如,我的 Typst 学习笔记就有这样的起手式:

#set document(
	author: "Me",
	title: "Learning Typst",
)//设定基础文档信息

#set text(
	font: "Sarasa UI SC",
  size: 10pt,
)//文本使用更纱黑体UI SC,字体大小为10pt

#set heading(
	numbering: "A.1. ",
)//为每个标题带上编号

#set par(
  justify: true,
)//两端对齐,西文允许断字对齐

#set outline(
	indent: true,
	depth: 2,
)//设定目录带有层级缩进,最深显示2级标题。

#show link: underline
//显示超链接的下划线

熟悉 \LaTeX 的一眼就能看得出来:这不就是每个文档开头都有的定义样式、引用模板、引用参考文献等等一堆命令吗?Typst 也继承了这一特性。

这个过程和 Word 用户新建文档之后调整样式、调整字体等等一样,只不过 Word 是用鼠标点的,而 Typst 用的是代码。这套代码被称为 Set 规则

通常来说,拿出 Set 规则就足够应对很多情况下的排版要求了。但是万一发现需要调整的属性没有,该怎么办?

一个很典型的例子就是为标题(Heading)单独设置字体。

在 Typst 当中设置全局字体非常简单,上面就已经写出来了:#set text(font:"Sarasa UI SC")。但是——如果真的这么写,就会发现整个文档连正文带标题都变成了更纱黑体。但是我想给所有的标题都改成西文使用 Noto Serif,中文使用 Noto Serif CJK SC,这个该怎么操作呢?

按照 set 规则的写法的假设,仿佛应该这么写:#set heading(font:("Noto Serif", "Noto Serif CJK SC"))。但是如果真的这么写的话,就会发现 Auto Complete 根本不工作,写出来也没效果还会报错。

原因很简单,我们可以去 Typst 官方文档看一眼 heading 函数的信息:level、depth、……bookmarked、hanging-indent。嗯?怎么没有字体设定?

这就是 Typst 和 CSS、Word 在思路上的一个小小的区别。需要始终牢记:heading 是一个函数。Typst 对于标题的基础写法是这样的:#heading()[Heading 1] 或者 #heading("Heading 1")。这个意思其实是用 heading 这个函数来处理“Heading 1”这个文本参数,而不是告诉解析器:“Heading 1”是标题。所以,Typst 只能针对 heading 这个函数当中的文本(text)来给出 set 规则,而 text 对象涵盖了所有的文本对象,那么该怎么办?

这就是 Typst 排版的第二个利器:show 规则。show 规则可以深度定制元素的外观,最常见的基本形式就是 show-set 规则。使用 show-set 规则只需要一句话就能够单独改变标题的字体:

#show heading: set text(font: ("Noto Serif", "Noto Serif CJK SC"))

这句代码的基本意思是将 Heading 中的 text 的 font 设定为后面两个字体(带 fallback)。

如果能够深入理解 Typst 的函数的运行哲学的话,其实就可以很迅速地通过官方文档和 Tinymist 的 Auto Complete 来给文档排版了。只需要想明白:

第一、需要调整哪个元素的什么样式?

第二、这个元素是哪里来的?如何操纵它的样式?是直接对元素函数的属性进行操作,还是对它的参数进行操作?

这就是 Typst 使用时最核心的两点。弄明白了这个之后,就会发现 Typst 并不困难。

Typst 虽然没有 \LaTeX 那样是一个高入云霄的庞然大物,但是也毕竟是一个完整的程序语言,指望一篇小小的短文介绍完它所有的特性显然是不现实的,何况这东西我自己也正在学习当中。以上只是对 Typst 的一些很简单的核心功能的介绍,如果有兴趣的话,不妨登上 Web App,或者装好本地环境,自己对着官方文档尝试自己写一些东西才是正经。

总结

Typst 的第一个正式版本发布于 2023 年 4 月 4 日,这是一个极为年轻的产品。创造它的是两个德国人。而且简介非常有趣:

We are two graduates from Berlin with a passion for software. Typst was born out of our frustration with LaTeX. Not knowing what we were in for, we decided to take matters into our own hands and started building. Four years later, Typst is almost ready to launch. 我们是两个来自柏林、对软件有热情的毕业生。Typst 诞生的原因是我们对 LaTeX 的失望。虽然不太清楚我们即将面对什么,我们还是决定自己动手,开始构建 Typst。四年后,Typst 终于差不多可以面世了。

虽然我前面一直在绷着这样一个基本概念:Typst 不是 Markdown,但是吧……认清现实吧,我自己就是将它视为 Markdown 和 \LaTeX 之间过渡地带的一个产品在用的。它也确实很好地填补了这两个产品之间的空白。对 \LaTeX,它的优势在于简练的语法、非常小巧精干但是效率极高的解析器(尤其是它还支持增量编译,这个在日常使用中是一个巨大的加分项),对 Markdown,它的功能更加全面,对大文档的支持稳定性更好。

我作为 \LaTeX 苦手,自然准备用 Typst 取代它和 Word,至于 Markdown,它仍然有自己的生态位,那就是——抄起来就写。

当然,作为一个非常年轻的语言,Typst 的未来还有很多的不确定性,我想它应该还是会变得更好吧。毕竟我观察了一下,从 0.6 到 0.9,有很多 Feature 上的变化,比如 CJK 的支持、中西文混排时的自动空格、对字体 Fallback 的支持等等,到我开始使用时的 0.12 版本,很多 Feature 都已经到了可以正常使用的程度了。虽然我很清楚这东西要取代 \LaTeX 可能性基本没有,但是没有任何疑问的是,它会带给用户一个全新的选择。随着 Typst 受欢迎的程度的增加,一些原本提供 \LaTeX 模板的地方现在也开始提供 Typst 了,也有一些共产主义战士制作了各大高校的论文模板的 Typst 版本开源在 GitHub 上。

还是希望 Typst 变得更好吧。我上次学东西学得这么开心已经不记得是啥时候了。

Tags |