Typst 插入图片:大小、图题、位置、并排与子图
Typst 插图的完整写法:image 的 width、height 和 fit,支持的图片格式(PNG、JPEG、GIF、WebP、SVG、PDF),figure 加图题和 @fig 引用,placement 让图片浮动、scope 横跨双栏,grid 并排,subpar 宏包做子图,图片按章编号,以及图片找不到等报错。
最后核对:2026-10-24Typst 插图和 LaTeX 一样分两件事:image 把图片文件放进来,figure 给它加图题和编号。和 LaTeX 最大的不同是,figure 默认不浮动:图片就排在源码所在的位置,除非你明确让它浮动。LaTeX 里让人困惑的"图片乱跑",在 Typst 里是一个需要主动打开的选项。本文和 LaTeX 插图一文的内容一一对应。
本文示例用的图片是几个手写的小 SVG 文件,打开示例就能编译,不需要另外上传图片。在 JointTex 里,Typst 项目边输入边编译;点 PDF 里的图片能跳到对应的源码,反过来也可以。
怎么插入一张图片
// title: Typst 插入图片
#set text(font: ("Libertinus Serif", "Noto Serif CJK SC"), lang: "zh", region: "cn")
= 直接插入图片
// image 的路径相对于当前 .typ 文件所在的文件夹
// 只给 width 时,高度按图片比例算出,图片不会变形
#image("bars.svg", width: 40%)
= 带图题、可引用的图片
// figure 给图片加编号和图题;图片的图题默认就在下方
// 标签写在 figure 后面,正文用 @fig:bars 引用
如@fig:bars 所示,第四组的数值最高。
#figure(
image("bars.svg", width: 60%),
caption: [各组的测量值],
) <fig:bars>
= 同时给出宽和高
// 宽高都给出、比例又和图片不同时,fit 决定怎么处理:
// "cover"(默认)放大到盖满整个框,多出的部分被裁掉;
// "contain" 整张图放进框里,留出空白;"stretch" 拉伸变形
#grid(
columns: 3,
column-gutter: 1em,
row-gutter: 0.5em,
align: center,
// box 画出边框,便于看清图片占的范围(inset 让边框留在图片外面)
box(stroke: 0.5pt, inset: 1pt, image("bars.svg", width: 4cm, height: 1.5cm, fit: "cover")),
box(stroke: 0.5pt, inset: 1pt, image("bars.svg", width: 4cm, height: 1.5cm, fit: "contain")),
box(stroke: 0.5pt, inset: 1pt, image("bars.svg", width: 4cm, height: 1.5cm, fit: "stretch")),
[`"cover"`], [`"contain"`], [`"stretch"`],
)
要点:
image("bars.svg")插入图片。路径相对于写这行代码的.typ文件所在的文件夹;以/开头的路径从项目根目录算起,如image("/figures/plot.svg")。- 支持的格式:PNG、JPEG、GIF、WebP、SVG、PDF。文件名要写全:写
image("plot")时,Typst 不会替你补上.png、.pdf去找plot.png,而是报文件找不到。 - 矢量图(SVG、PDF)放大不失真,用 Python、MATLAB 等画的图优先导出为 SVG 或 PDF。插入多页 PDF 的某一页用
page参数,如image("plots.pdf", page: 2)。 - 不支持 EPS。插入
.eps文件会报error: unknown image format,要先转换成 PDF 或 SVG。 - 路径分隔符只能写
/。从 Windows 复制来的"figs\\plot.png"会报error: path must not contain a backslash。 - 图片放在子文件夹时,路径里写上文件夹,如
image("figures/plot.svg")。
图片大小怎么调
| 写法 | 作用 |
|---|---|
width: 80% | 宽度为所在容器宽度的 80%,高度按比例缩放 |
height: 5cm | 高度为 5cm,宽度按比例缩放 |
width: 4cm, height: 3cm | 宽高都固定,比例不同时由 fit 决定怎么处理 |
| 都不写 | 按图片自身的尺寸,但不超出可用的宽度 |
- 百分比相对于图片所在的容器:在正文里就是版心宽度,相当于 LaTeX 的
\linewidth;在grid的一格里就是这一格的宽度。推荐用百分比而不是写死的厘米数,换模板、改成双栏时图片会跟着版面变化。 - 宽和高都给出时,
fit有三个值:"cover"(默认)放大到盖满整个框,多出的部分裁掉;"contain"把整张图放进框里,留出空白;"stretch"拉伸到正好填满,图片会变形。示例的第三部分把三种效果并排画了出来。 - 旋转用
rotate(90deg, reflow: true, image(...))。注意 Typst 的正角度是顺时针,和 LaTeXangle=90的逆时针相反。reflow: true让排版按旋转后的尺寸占位;不写时仍按旋转前的尺寸占位,图片可能和周围的内容重叠。
图题和交叉引用怎么写
- 把图片放进
figure,用caption:写图题。Typst 默认把图题放在下方,正好符合图题在下的习惯。表题要放在上方,做法见 Typst 表格。 - 标签写在
figure(...)的右括号后面,如<fig:bars>;正文用@fig:bars引用。文档设置了#set text(lang: "zh")时,图题前缀是"图",引用得到"图 1";不设时是"Figure 1"。 - 引用前后的空格:
@fig:bars后面要留一个空格或紧跟中文标点,因为标签名里可以有汉字,如@fig:bars所示会被当成一个名为fig:bars所示的标签;前面不要留空格,否则会多排出一个空格。 - 不需要像 LaTeX 那样多编译一次才能得到编号,也不会出现
??:标签写错时直接报错error: label `<fig:bar>` does not exist in the document。 - 改图题格式,如编号后用冒号、图题小一号字:
#set figure.caption(separator: [:])、#show figure.caption: set text(size: 9pt)。
图片会不会跑到别的页面
默认不会。figure 的 placement 参数默认是 none,图片和普通段落一样,排在源码的位置;当前页放不下时,整个图移到下一页,当前页底部留下空白。想让它像 LaTeX 的浮动体那样移动,就设置 placement:
| 参数 | 含义 |
|---|---|
none(默认) | 不浮动,就在源码的位置 |
auto | 在页面(双栏时是所在栏)顶部和底部中,选离源码位置近的一个 |
top | 浮动到页面(双栏时是所在栏)顶部 |
bottom | 浮动到页面(双栏时是所在栏)底部 |
- 这几个值是对齐方式,不是字符串:写
placement: top,不要写placement: "top",后者会报error: expected alignment, auto, or none, found string。 - 浮动的图和正文之间的距离由
place的clearance参数控制,例如#show figure: set place(clearance: 1em)。 - 没有 LaTeX 的
p(单独成页)和!,也不需要float宏包的[H]:不写placement本来就是"放在这里"。 - 浮动的不一定是图。任何内容都可以用
place浮动,要同时写出位置(top、bottom或auto)和float: true,如#place(bottom + center, float: true)[...]。只写float: true不写位置时报error: vertical floating placement must be `auto`, `top`, or `bottom`。
写论文时,正文用"如@fig:bars 所示"引用,而不是"如下图所示",这样无论图片放在哪里都不影响阅读。
双栏论文里,图片怎么横跨两栏
#set page(columns: 2) 把页面分成两栏后,figure 只有一栏宽,width: 100% 指的也是一栏的宽度。需要横跨两栏时,给 figure 加 scope: "parent",相当于 LaTeX 的 figure*:
// title: Typst 双栏排版中的图片位置
#set text(font: ("Libertinus Serif", "Noto Serif CJK SC"), lang: "zh", region: "cn")
// 整页分成两栏
#set page(columns: 2)
#set par(first-line-indent: (amount: 2em, all: true), justify: true)
// 一段反复使用的占位正文
#let filler = [这是一段占位正文,用来把栏填满,便于观察图片的位置。双栏排版里,图片默认只有一栏宽。]
// placement: top 让图片浮动到页面顶部;
// scope: "parent" 让它跳出当前这一栏,横跨两栏。宽度 100% 此时指整个版心的宽度
#figure(
image("wide.svg", width: 100%),
placement: top,
scope: "parent",
caption: [横跨两栏的图],
) <fig:wide>
#filler
#filler
// 不写 placement 时,图片就排在源码所在的位置,不会移动
#figure(
image("bars.svg", width: 100%),
caption: [一栏宽的图,留在原处],
) <fig:here>
#filler
#filler
// placement: auto 由 Typst 在当前栏的顶部和底部之间挑一个离源码位置近的
#figure(
image("bars.svg", width: 80%),
placement: auto,
caption: [自动放到栏的顶部或底部],
) <fig:auto>
@fig:wide 在页面顶部横跨两栏;@fig:here 留在源码的位置;@fig:auto 在这里浮动到了第一栏的底部。
#filler
#filler
#filler
scope: "parent"只对浮动的图有效,必须同时写placement: top或bottom(或auto)。不写placement时会报error: parent-scoped placement is only available for floating figures。- 横跨两栏时,
width: 100%指的是整个版心的宽度。 - 表格横跨两栏的写法完全相同,把
scope: "parent"加在装表格的figure上。
两张图怎么并排
两张图各自编号(图 1、图 2)时,用 grid 把一行分成几格,每格放一个 figure。图和表并排也一样,装着表格的那个 figure 自动得到"表"的编号:
// title: Typst 图片并排
#set text(font: ("Libertinus Serif", "Noto Serif CJK SC"), lang: "zh", region: "cn")
// 表题在上,图题保持默认的下方
#show figure.where(kind: table): set figure.caption(position: top)
= 两张图各自编号
// grid 把页面横向分成两列,每一格放一个 figure,编号各自独立
// image 的 width: 100% 指的是所在这一格的宽度
#grid(
columns: (1fr, 1fr),
column-gutter: 1em,
[#figure(image("bars.svg", width: 100%), caption: [柱状图]) <fig:bars>],
[#figure(image("curve.svg", width: 100%), caption: [曲线图]) <fig:curve>],
)
@fig:bars 和@fig:curve 并排放置,各有自己的编号。
= 图和表并排
// 表格放进 figure 后自动得到“表”的编号,与图的编号分开计数
// align: horizon 让两格在垂直方向居中对齐
#grid(
columns: (1fr, 1fr),
column-gutter: 1em,
align: horizon,
[#figure(image("curve.svg", width: 100%), caption: [测量曲线]) <fig:side>],
[#figure(
table(
columns: 2,
[时间], [数值],
[1], [0.12],
[2], [0.58],
[3], [0.33],
),
caption: [曲线上的几个点],
) <tab:side>],
)
图和表分开计数:右边的表格是@tab:side,左边的图是@fig:side。
columns: (1fr, 1fr)分成等宽的两格,column-gutter: 1em是两格之间的距离。三张图并排就写三个1fr。- 每格里的
image(..., width: 100%)是这一格的宽度。 - 示例把每个
figure和它的标签一起写在一个内容块里:[#figure(...) <fig:bars>]。 grid默认把各格顶端对齐;图和表高度不同时,align: horizon让它们在垂直方向居中。
子图怎么做:图 1(a)、图 1(b)
几张图共用一个编号、各自带 (a)(b) 小标题时,可以用 Typst Universe 上的 subpar 宏包。JointTex 支持 Typst Universe 的宏包,#import 之后直接使用:
// title: Typst 子图(subpar)
// subpar 是 Typst Universe 上的宏包,第一次编译时自动下载
#import "@preview/subpar:0.2.2"
#set text(font: ("Libertinus Serif", "Noto Serif CJK SC"), lang: "zh", region: "cn")
// subpar.grid 的用法和 grid 相似:每一格是一个 figure,后面可以跟它自己的标签;
// caption 和 label 是整张图的总图题和标签
#subpar.grid(
figure(image("bars.svg"), caption: [柱状图]), <fig:sub-bars>,
figure(image("curve.svg"), caption: [曲线图]), <fig:sub-curve>,
columns: (1fr, 1fr),
gutter: 1em,
caption: [两种图形的对比],
label: <fig:compare>,
// 引用子图时显示的编号格式,默认是 "1a",这里改成 "1(a)"
numbering-sub-ref: "1(a)",
)
@fig:compare 由两张子图组成:@fig:sub-bars 是柱状图,@fig:sub-curve 是曲线图。
// 子图不占用图的编号:下一张图是图 2
#figure(image("bars.svg", width: 50%), caption: [另一张图]) <fig:next>
@fig:next 的编号紧接在@fig:compare 之后。
subpar.grid的用法和grid相似:每一格写一个figure,后面紧跟它的标签;caption和label是总图题和总标签。- 子图的小标题默认是"(a) 柱状图"的样子;引用子图默认得到"图 1a",示例用
numbering-sub-ref: "1(a)"改成了"图 1(a)"。 - 子图不占用图的编号,后面的图从图 2 继续。
#import必须写明版本号,这里是0.2.2,所以宏包出了新版本,文档也不会变样。
图片能不能按章编号:图 1-1
学位论文常要求图按章编号,如第 2 章的第一张图是"图 2-1"。用下面三条规则就能做到:
#set heading(numbering: "1.1")
// 每一章开头把图和表的计数器清零
#show heading.where(level: 1): it => {
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
it
}
// 编号由章号和图在本章中的序号组成
#set figure(numbering: n => numbering("1-1", counter(heading).get().first(), n))
图题和引用都会变成"图 1-1""表 2-1"。counter(figure.where(kind: image)) 是图的计数器,表格的计数器要单独清零。
报错:图片找不到
error: file not found (searched at …/figs/plot.png)
括号里是 Typst 实际去找的位置,把它和项目里的文件对照,通常一眼就能看出问题。最常见的原因:
- 路径相对于当前文件,不是主文件。 在
chapters/intro.typ里写image("figs/plot.png"),找的是chapters/figs/plot.png。这和 LaTeX 不同:LaTeX 的路径总是相对于主文件。不想操心时,用/开头的路径从项目根目录算起,如image("/figs/plot.png")。 - 大小写不一致。 Typst 按文件名的大小写精确查找,
Plot.PNG和plot.png是两个文件。 - 文件没有上传,或者上传到了别的文件夹。
另外两种报错:
error: unknown image format:文件不是 Typst 支持的图片格式,常见于 EPS,或者把别的文件当成了图片。error: failed to decode image (...):Typst 按扩展名读取文件,文件已经损坏,或者内容和扩展名不符(比如把 PNG 改名成了.jpg)时报这个错。
在 JointTex 里,报错会在源码对应位置画上波浪线,并列在"问题"标签页中。更多报错见 Typst 常见报错。
本文示例都可以在 JointTex 中一键打开为新项目,把示例里的 SVG 换成自己上传的图片即可。
