Typst 插入图片:大小、图题、位置、并排与子图

Typst 插图的完整写法:image 的 width、height 和 fit,支持的图片格式(PNG、JPEG、GIF、WebP、SVG、PDF),figure 加图题和 @fig 引用,placement 让图片浮动、scope 横跨双栏,grid 并排,subpar 宏包做子图,图片按章编号,以及图片找不到等报错。

最后核对:2026-10-24

Typst 插图和 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"`],
)
Typst 插入图片在 JointTex 中打开

要点:

  • 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 的正角度是顺时针,和 LaTeX angle=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
Typst 双栏排版中的图片位置在 JointTex 中打开
  • 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。
Typst 图片并排在 JointTex 中打开
  • 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 之后。
Typst 子图(subpar)在 JointTex 中打开
  • 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 换成自己上传的图片即可。

JointTex

Browser compatibility

JointTex could not start

The application did not finish loading. Check your connection, then try again.

Supported versions

  • Chrome and Edge 88 or later
  • Firefox 84 or later
  • Safari and iOS Safari 14 or later
Reload