用 LaTeX 画出来的图,在导出之前本来就是矢量图——坐标轴是曲线,标签是字体里的字形。可是一旦要导出为图像,这些信息会被轻易丢掉。而决定丢不丢的那一步,比多数人以为的更靠前:就是走 DVI 还是走 PDF 这一个选择。dvisvgm 的手册写得很直白:PDF 和 PostScript 文件中的文字,总是会被转换成路径元素。 也就是说,一旦经过 PDF,图里的文字就不再是文字。本页正是围绕这一点来安排 standalone、pdfcrop、dvisvgm 以及栅格化。
只把图放进一页——standalone 类
导出的第一步,是让纸张的大小等于图的大小。Martin Scharrer 的 standalone 类(TeX Live 2024 收录 v1.3b)会把内容的尺寸直接当作页面尺寸。这里把 $\displaystyle \int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$ 单独放进 \documentclass[border=2pt,preview]{standalone} 后用 pdflatex 编译,生成 PDF 的页面尺寸是 85.779 × 27.993 pt——正是这个公式的外框。不写 border 时四边默认是 0pt。类文件本身写死的是 0.50001bp 这个奇怪的精确值(用意是防止舍入把字形边缘削掉),但随附的 standalone.cfg 会在处理选项之前用 \standaloneconfig{border=0pt} 覆盖掉它。若是 TikZ 图形,用 \documentclass[tikz,border=2pt]{standalone},把 tikzpicture 直接放在 \begin{document} 之后即可。要截取一段需要换行的正文片段,就用 varwidth=10cm 这样给出宽度。standalone 内部使用的 preview 宏包原本是为 Emacs 的 AUCTeX 中的 preview-latex 而写的——「只截取文档的一小块来排」这个想法正是从那里来的。
% fig.tex — a figure that is its own page
\documentclass[tikz,border=2pt]{standalone}
\begin{document}
\begin{tikzpicture}
\draw[thick,->] (0,0) -- (3,0) node[right] {$x$};
\draw[thick,->] (0,0) -- (0,2) node[above] {$y$};
\draw[blue,thick] (0,0) .. controls (1,2) .. (3,1);
\end{tikzpicture}
\end{document}裁掉现成 PDF 的空白——pdfcrop
当图形嵌在普通文档里、无法重新生成时,可以用 pdfcrop。它是 Heiko Oberdiek 写的 Perl 脚本(TeX Live 2024 收录 v1.42),会算出页面内容的外接矩形并把裁剪框收到那里。实测中,一页 letter 尺寸 612 × 792 pt 的文档经过 pdfcrop doc.pdf doc-crop.pdf 后变成 345 × 539 pt。若不想让边距为零,可以写 --margins 10,四边各留回 10 pt,得到 365 × 559 pt。有一点要注意:pdfcrop 会调用 Ghostscript——脚本开头就写着「Requirements: Perl5, Ghostscript」。没装 gs 的环境跑不起来。另外,如果本来就能用 standalone 生成这张图,那就根本不需要 pdfcrop:不产生边距比事后裁掉更快,也更准。
pdfcrop doc.pdf doc-crop.pdf # 612x792 pt becomes 345x539 pt
pdfcrop --margins 10 doc.pdf doc-crop.pdf # keep 10 pt on every side
pdfcrop --hires doc.pdf doc-crop.pdf # use the high-resolution bounding box做成 SVG——dvisvgm,以及 DVI 路线为什么更好
要放到网上的图,SVG 是首选。Martin Gieseking 的 dvisvgm(TeX Live 2024 收录 3.2.2)能把 DVI、EPS 和 PDF 转成 SVG,但从哪个入口进去,出来的东西不一样。把 \def\pgfsysdriver{pgfsys-dvisvgm.def} 放在 \documentclass 之前,用 latex 生成 DVI,再跑 dvisvgm fig.dvi,得到的 SVG 里坐标轴和曲线是 <path>,而 $x$、$y$、$y=f(x)$ 这些标签是 <text> 元素——文字仍是文字,引用嵌在 <defs> 里的矢量字体。可从 PDF 进去就不是这样了。dvisvgm 的手册明说:PDF 和 PostScript 文件中的文字总是会被转换成路径元素。实测也是如此:用 poppler 的 pdftocairo -svg 把同一张图的 PDF 转成 SVG,<text> 元素为零。
% figdvi.tex — the DVI route, which keeps the labels as real text
\def\pgfsysdriver{pgfsys-dvisvgm.def}
\documentclass[tikz,border=2pt]{standalone}
\begin{document}
\begin{tikzpicture}
\draw[thick,->] (0,0) -- (3,0) node[right] {$x$};
\draw[blue,thick] (0,0) .. controls (1,2) .. (3,1);
\end{tikzpicture}
\end{document}latex figdvi.tex # produce a DVI, not a PDF
dvisvgm figdvi.dvi # SVG with <text> elements and vector paths
# the PDF route needs a helper and outlines all text:
dvisvgm --pdf fig.pdf # requires mutool with Ghostscript 10.01 or newer想从 PDF 做 SVG,还有一个现实障碍。dvisvgm --pdf 从前是把 PDF 交给 Ghostscript 处理的,但按手册所说,Ghostscript 10.01.0 引入的新 PDF 解释器让这条路走不通了,于是 dvisvgm 改为寻找 MuPDF 的 mutool。在撰写本文的机器上(Ghostscript 10.03.0,没有 mutool),执行 dvisvgm --pdf fig.pdf 会停在 ERROR: can't retrieve number of pages from file fig.pdf。也就是说,PDF 路线要求额外依赖;而且只要是交给 Ghostscript 处理,按手册所述文字必定被转成轮廓——选 DVI 路线的理由有两重。还有一个值得记住的选项是 -e(--exact-bbox):默认用 TFM 的度量来定外接矩形,若字形实际超出这些数值,边缘就可能被切掉。图边上的字缺了一块时,先加上它。
SVG 里的字体怎么办——--font-format 与 --no-fonts
能保住 <text> 是好事,但还得再做一个决定。dvisvgm 的默认是 SVG 字体(把 <font> 和 <glyph> 嵌进 <defs>),这种格式规范里有,实际支持它的环境却不多——dvisvgm 自己的手册在 --no-fonts 条目下就写着,用路径输出「与多数不支持 SVG 字体的应用更兼容」。用同一张 TikZ 图比较三种做法:默认的 SVG 字体是 5,790 字节,--font-format=woff2 是 4,815 字节(把 WOFF2 以 data URI 嵌进 @font-face 的 src),--no-fonts 是 8,290 字节,<text> 元素为零,所有字形都变成 <path>。实用判断很简单:要放到网上就用 --font-format=woff2——文字可选中,浏览器一定画得出来;要交给 Illustrator、Inkscape、PowerPoint 等其他软件就用 --no-fonts——体积变大,但不会有渲染出错的余地。
| 选项 | SVG 内的文字 | 同一张图的大小 | 适合的用途 |
|---|---|---|---|
--font-format=svg | <text> 加 SVG 字体(默认) | 5,790 字节 | 支持面窄,实务中一般不选 |
--font-format=woff2 | <text> 加嵌入 @font-face 的 WOFF2 | 4,815 字节 | 网页发布:文字可选中,体积最小 |
--no-fonts | 没有 <text>;所有字形都是 <path> | 8,290 字节 | 交给其他绘图软件时:不会显示出错 |
做成 PNG——给幻灯片和老环境用
仍有不少地方不接受 SVG,这时就退回到 PNG。分辨率用 -r 指定,300 dpi 视为下限,印刷或高分屏则以 600 dpi 为目标。把上面生成的 85.779 × 27.993 pt 公式 PDF 以 600 dpi 栅格化,无论用 Ghostscript(gs -sDEVICE=pngalpha -r600)还是用 poppler 的 pdftocairo -png -r 600 -transp,都得到约 715 × 233 像素的透明 PNG。因为 72 pt 等于 1 英寸,85.779 ÷ 72 × 600 ≈ 715;算式对不对,正好用来确认分辨率参数是否真的生效。若不需要透明,用 -sDEVICE=png16m(pdftocairo 则去掉 -transp)会得到白底。很多文章推荐 ImageMagick 的 magick -density 300 in.pdf out.png,但 ImageMagick 会把 PDF 的渲染委托给 Ghostscript。它不属于 TeX Live,本文所用机器上也没有安装,因此未验证其行为。既然真正作画的是 Ghostscript,直接调用它就少一层依赖。
# Ghostscript, transparent background, 600 dpi
gs -q -dNOPAUSE -dBATCH -sDEVICE=pngalpha -r600 \
-dTextAlphaBits=4 -dGraphicsAlphaBits=4 \
-sOutputFile=eq.png eq.pdf
# poppler, same result, shorter to type
pdftocairo -png -r 600 -transp -singlefile eq.pdf eq一条命令直出 PNG——standalone 的 convert= 与 --shell-escape
若嫌每次都要敲三条命令,可以交给 standalone 的类选项 convert=。排版一结束,类自己就把转换命令扔给 shell。后端可选 imagemagick(默认)、ghostscript、pdf2svg,分辨率默认是 density=300。即使没装 ImageMagick,指定 ghostscript 也能用——实测中写成 \documentclass[border=2pt,convert={ghostscript,density=600,outext=.png}]{standalone} 并运行 pdflatex --shell-escape eqc.tex,日志里出现 gs -dSAFER -dBATCH -dNOPAUSE -sDEVICE=png16m -r600 -sOutputFile=eqc.png eqc.pdf,715 × 233 的 PNG 一条命令就出来了。不过它要启动外部程序,受限的 shell escape 不够,必须用 --shell-escape。图多的项目里,把转换放到构建脚本一侧更稳妥,免得养成常开 --shell-escape 的习惯。
% eqc.tex — build the PNG in the same run
\documentclass[border=2pt,convert={ghostscript,density=600,outext=.png}]{standalone}
\usepackage{amsmath}
\begin{document}
$\displaystyle \int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$
\end{document}
% then: pdflatex --shell-escape eqc.tex把上面这一整套流程——只排那一小段、裁掉空白、再落成矢量或位图——包进图形界面的,就是 TeX2img。粘上公式,选好格式和分辨率,透明和抗锯齿它都替你处理。做一次性导出时它快得无与伦比;但若文档里有几十张图、还要反复改版,把前几节的命令编进构建流程反而更省事。另外 TeX2img 是一个独立应用,不属于 TeX Live,本文所用机器上也没有安装,因此未验证其行为。
该导出成哪种格式
- 要放到网上的图 → SVG。用
latex生成 DVI,再走dvisvgm --font-format=woff2。文字仍留在<text>里,放大也不劣化。 - 要再放回 LaTeX 文档的图 → 保持 PDF。用
standalone生成就没有边距,可直接交给\includegraphics。 - 幻灯片、Word、社交媒体 → PNG,用
gs -sDEVICE=pngalpha -r600或pdftocairo -png -r 600 -transp。只有想要白底时才去掉透明。 - 要在 Illustrator 或 Inkscape 里加工的图 → 用
--no-fonts输出 SVG。即使对方没有那套字体,形状也不会变。 - 含照片的图 → 只有照片部分用 JPEG,线条和标签用 SVG 或 PNG。整张做成 JPEG 会让文字边缘变脏。
- 当
dvisvgm --pdf跑不起来时 → 先改走 DVI 路线。若非得从 PDF 出发,就装上 MuPDF 的mutool。