typst
typst
Typst is a new markup-based typesetting system that is designed to be as powerful as LaTeX while being much easier to learn and use.
非官方中文指南,我一般拿来快速找第三方包
typst 并没有一个给开发者的清晰的文档。。examples book可以抄点东西。
我不是很喜欢 latex,所以尝试使用 typst 作为我的论文排版工具。
优点:
- typst 使用 rust 语言编写,编译极快,几乎秒出
- 据群聊天记录所传,latex 编译 30min 的 typst 只要 46s
- 小,二进制也就数十 MB;第三方包只有源码,约等于不占空间
缺点:
- 新兴工具,bug 较多
- 社区不够完善,网上模版/教程不多,文档很烂
- 面向 user 的文档还行,但是面向 developer 的…一点没有。
- 自创的 DSL 比较折磨(这是一个连标准输出都没有的弱类型语言(src),debug 很容易红温)
- 要我说,还不如直接用 rust(但是这样二进制大小也压不下来)
安装与配置
基础用法是 CLI 使用,但我配合 VSCode 使用。
CLI:进 release 下载,解压后把
typst.exe
直接丢进C:\Windows\System32
就行。- 或者也可以
scoop install typst
一行搞定。 - 然后就可以
typst watch example.typ
,自动生成 pdf。
- 或者也可以
vscode 扩展:
- 使用 vscode-pdf 查看 pdf。
- 我不推荐 Typst Preview。
- 曾经遇到过无法生成的 bug;
- 很慢,10 页的 pdf 就会卡了。
- 我不推荐 Typst Preview。
- LSP新起之秀
- 安装 Tinymist Typst - Myriad Dreamin
ctrl + ,
进入设置:- Tinymist: ExportPdf 改为 onSave
- Tinymist: FormatterMode 改为 typstyle(也是一个新起之秀的 formatter)
传统官方插件:Typst LSP
- 缺点:慢,没有 formatter
- 优点:0 配置,懒人福音
- 使用 vscode-pdf 查看 pdf。
然后就可以愉快地敲论文了。每次保存时会自动生成 pdf,拖到侧边就能看了。
资源
编译导出
typst compile xxx.typ # 基础编译指令
typst compile --root .. xxx.typ # 如果需要 include 父级目录的文件,则需要指定 root
typst compile --format svg xxx.typ '{n}.svg' # 导出为 svg 格式
基础
这里有许多大学的毕业论文模版,多抄抄就会用了
简单来说,[]
内是正文(content
),{}
内是代码,()
是数组(array
),正文调用函数要加 #
,代码里可以直接调。
关键字也就 set
和 show
常用,set
设置作用域内的属性,show
相当于每次使用都调用某个函数。
剩下的 let
,if
什么的都是 rust 的东西,这里不说(
数组
typst 没有 list
类型,只有 array
。
("12")
这样其实还是 string 类型,如果要数组类型需要 ("12",)
字体字号
我一般用:
#let 字号 = (
初号: 42pt,
小初: 36pt,
一号: 26pt,
小一: 24pt,
二号: 22pt,
小二: 18pt,
三号: 16pt,
小三: 15pt,
四号: 14pt,
中四: 13pt,
小四: 12pt,
五号: 10.5pt,
小五: 9pt,
六号: 7.5pt,
小六: 6.5pt,
七号: 5.5pt,
小七: 5pt,
)
#let 字体 = (
仿宋: ("Times New Roman", "FangSong"),
宋体: ("Times New Roman", "Songti SC", "Songti TC", "SimSun"),
黑体: ("Times New Roman", "SimHei"),
楷体: ("Times New Roman", "KaiTi"),
代码: ("Fira Code", "Consolas", "monospace", "WenQuanYi Zen Hei Mono", "FangSong"),
)
#let 中文数字(num) = {
("零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十").at(int(num))
}
数学
基本上够用了。
表格
- tablem,类 markdown 语法的表格,简单快速,功能不强。
- 无法通过
\|
转义打出|
字符 - 超出列数报错(与 markdown 行为不同;在 markdown 中会直接忽略)
- 无法通过
- tablex,更麻烦但更强大的表格。
- 如果表格中含有粗体斜体,批量处理就比较麻烦。(ex) 我的解法,甚至还发现了个 bug?
- 这个预计算是很难改的(源码访问
at
时的default
已经是Option<Value>
了)。说到底,根本问题还是 typst 选择自创的这个 DSL 的问题,你像 rust 那样 Option 套or_else
哪有这么多事。
- 这个预计算是很难改的(源码访问
- 如果表格中含有粗体斜体,批量处理就比较麻烦。(ex) 我的解法,甚至还发现了个 bug?
- excel 表格转为 typst 表格,支持合并的单元格,很好用。可惜 windows only。
heading
heading 算是 typst 里比较重要的一个东西,平常用的真不少。
numbering
默认的 numbering 会显示全路径,例如
= h1 // 1
== h2 // 1.1
=== h3 // 1.1.1
但是在中文语境下我们很可能不需要它显示全路径。这时可以使用这样的 pattern(抄来自己修改的):
set heading(numbering: "1.1.1.1")
show heading: it => locate(loc => {
set text(font: 字体.黑体)
set par(first-line-indent: 0pt)
let levels = counter(heading).at(loc)
let deepest = if levels != () {
levels.last()
} else {
1
}
if it.level == 1 {
set text(字号.四号)
if it.numbering != none {
numbering("一、", deepest)
}
it.body
} else if it.level == 2 {
set text(size: 字号.小四)
if it.numbering != none {
numbering("1.1 ", ..levels.slice(1))
h(3pt, weak: true)
}
it.body
} else if it.level == 3 {
set text(size: 字号.五号)
if it.numbering != none {
numbering("1.1.1 ", ..levels.slice(1))
h(3pt, weak: true)
}
it.body
}
})
效果:
= h1 // 一、
== h2 // 1
=== h3 // 1.1
居中
有时候我们需要换页,并将某个标题居中。我以前喜欢改 heading level == 1 的 show rules 来完成这件事,现在发现还是自己写一个 title 函数比较好。
#let title(title) = {
pagebreak(weak: true)
align(center)[#text(size: 字号.二号, font: 字体.黑体, title)]
}
代码
大段代码不要直接写 typ
文件里,最好从外部引用,解耦,还方便扔 formatter。小段就无所谓了。放个我的带边框代码块:
// 带边框代码块
#let frame(title: none, body) = {
let stroke = black + 1pt
let radius = 5pt
let txt = (font: 字体.代码)
set text(..txt)
let name = block(
breakable: false,
fill: color.linear-rgb(0, 0, 0, 10),
stroke: stroke,
inset: 0.5em,
below: -1.5em,
radius: (top-right: radius, bottom-left: radius),
title,
)
block(
stroke: stroke,
width: 100%,
inset: (rest: 0.5em),
radius: radius,
)[
#if title != none {
place(top + right, dx: radius + stroke.thickness, dy: -(radius + stroke.thickness), name)
}
#body
]
}
// 引入外部代码块
#let include_code(file_path) = {
let name = file_path.split("/").at(-1)
let lang = name.split(".").at(-1)
frame(title: name)[
#raw(read(file_path), lang: lang)
]
}
我的折腾历程
从 #1494 摸了个好看的代码块样式来,然后自己改改,就是下面的了。
#let frame(title: none, body) = {
let stroke = black + 1pt
let radius = 5pt
let font = (font: "Fira Code", size: 10pt)
let name = block(
breakable: false,
fill: color.linear-rgb(0, 0, 0, 10),
stroke: stroke,
inset: 0.5em,
below: -1.5em,
radius: (top-right: radius, bottom-left: radius),
title,
)
set text(..font)
show raw: set text(..font)
box(stroke: stroke, radius: radius)[
#if title != none {
align(top + right, name)
}
#block(
width: 100%,
inset: (rest: 0.5em),
body,
)
]
}
#let include_code_file(file_path, name, lang) = {
frame(title: name)[
#raw(read(file_path), lang: lang)
]
}
这样用 box 包的代码块有一个致命缺陷:若 box 高度大于剩余页面高度,则会自动换页;若 box 高度大于整个页面的高度,则超出部分不会显示。因此只适合用来引用小块代码,否则就别想要边框了。我去其他地方寻找解法,BUAA 的用 figure 包的也会有这个问题。
然后偷窥交流群发现,如果不需要 name
参数的话,简单用 block
包一下就能实现自动切割。抄来的代码用的是 box
,因此才会自动换页。
#let 字体 = (代码: ("Fira Code", "Times New Roman", "SimSun"))
#let frame(body) = {
set text(font: 字体.代码)
block(
stroke: black + 1pt,
width: 100%,
inset: (rest: 0.5em),
radius: 7pt,
body,
)
}
// 使用方法:
#frame[
```js
console.log("1")
```
]
伪代码
目前在用algorithmic,并且修了个 bug。
不过目前看来,还是lovelace更泛用一点。
目录
众所周知,中文报告一般会要求页码遵循 第 x 页,共 x 页
的格式。这没问题,在 set page(numbering: (..nums) => ...)
即可。
然而 typst 的目录(outline
)默认使用页面的页码格式,也就是说,会出现这样的情况:
我尝试问群友,没人理 几个月后才又聊到这个话题;尝试看源码,发现写死了;最后在 repo 里乱搜,居然被我搜到了一个究极自定义方案,改一改就是解法。
box
如果你想要好看的框(box),不需要自己写。
- showybox:好看,强大
- typst-boxes:简易
- gentle-clues:常用框
bug
这东西 bug 其实还真不少。。
中文粗体斜体
广为诟病的一条了,typst 不支持伪粗体伪斜体。不过据说是会修。
参考文献
从 .bib
引入参考文献时,如果文献类型是 thesis
,本该在适当位置加 [D]
的,然而它不会加。。
感觉是 hayagriva 的锅,但是我翻了下源码发现看不懂。爆了!
看了一下 issue 时间,3 days ago。。那我这次课程论文寄了,唉,扣点分不重要
其他大学模版的解法是用 python 写了个处理脚本,通过 CSL 解析,半自动加参考文献。
后续:修了,0.10.0 正常。
缩进
中文等语言需要在每行开头缩进两个宽字符。typst 提供了 par()
控制缩进行为,但是在标题下面一行的文字却不会被缩进。。
所以下面有人提供了一个解法:
show heading: it => {
it
par()[#text(size:0.5em)[#h(0.0em)]]
}
这个方法在上一行是 figure 啊,raw 啊什么的时候还是无法缩进,如果手动 linebreak()
的话又多出了不必要的间距,太丑了。