standalone 是一个 LaTeX 类,用来把单独一样东西——一张图、一幅 TikZ 绘图、一个表格——排成紧贴内容尺寸裁切的一页。它真正的价值与其说是裁切,不如说是 一个文件有两副面孔:单独编译 figure.tex 会得到只含该图的 PDF;从论文里 \input 它,同一个文件又能一字不改地流入正文。裁切机制也有来历:在如今作为默认的 crop 选项之前,standalone 使用的是从 Emacs 的 AUCTeX 中成长起来的 preview 宏包——正是那套在编辑器里浮出公式预览的代码。本页讲的是类和宏包本身:border= 如何生效、multi 与 subpreambles,以及实际会遇到的错误。把做好的图转成 PNG 或 SVG,是另一页的题目。
同一个图文件,单独用也能并入正文
文档一大,就会想把图拆到单独的文件里。可要单独编译这样的文件,每次都得补上 \documentclass、\begin{document} 这层外壳,而且输出只是图停在一张正文用大白页的角落。standalone 类一次解决这两件事。在图文件开头写上 \documentclass{standalone},它就能单独编译,输出会 紧贴内容大小裁切 成一页(PDF、DVI 或 PS),没有页码、页眉和页脚。
% figure.tex — a figure that is its own document
\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}standalone 有两块招牌:在图文件里使用的 类(\documentclass{standalone}),和在主文档里加载的 宏包(\usepackage{standalone})。类负责「把一块单独排出来」,宏包负责「把那一块并入正文」。作者是 Martin Scharrer,TeX Live 2024 里的版本是 2022 年 10 月的 v1.3b。依赖方面,类需要 xkeyval,宏包在此之外还需要 currfile、gincltex、filemod、adjustbox,它们都随 TeX Live 和 MiKTeX 提供。
crop 与 preview 的区别,以及默认值到底在哪里定
默认是 crop,边距为 0pt。 但这个默认值写在 配置文件 standalone.cfg 里,而不在类文件中。standalone.cls 本身把 preview 和 0.50001bp 设为默认(这是 v0.x 的行为);随后,就在选项处理开始之前,standalone.cfg 被读入,用 \standaloneconfig{crop} 和 \standaloneconfig{border=0pt} 把两者覆盖掉。这种两段式安排很有用:把你自己的 standalone.cfg 放进项目目录或本地 TEXMF 树,就能 改变该环境下所有 standalone 文件的默认值。发行包里的 cfg 每次更新都会被覆盖,所以务必把自己的设置另存一处。
crop 与 preview 互斥:两个都写时 后写的胜出,而且任何一个都会连带设定 float=false。真正的差别一量就出来。在 TeX Live 2024 上排一个只含 2 cm × 1 cm 矩形的图,默认的 crop 得到 57.09 × 28.75 bp。此时若在 \end{document} 前加入一个空行,crop 下毫无变化,但指定 preview 时宽度会跳到 343.71 bp:空行被当作分段,于是内容变成了宽度等于 \linewidth 的一个段落。「图右侧出现大片空白」这一经典症状就是这么来的,而 crop 成为默认,正是为了避开它。preview 之所以保留,是因为在 XeLaTeX 下 TikZ 渐变出问题时,需要一条退路。
用 border= 添加边距,以及数值如何解读
最常用的选项是 border=(别名 margin=)。写一个值时四边通用,两个值时分别对应水平与垂直,四个值时按左、下、右、上的顺序生效。要传多个以空格分隔的值,就把整体放进花括号:border={10pt 5pt}。不带单位的裸数值按 bp(PostScript 点)解读。在刚才那张 57.09 × 28.75 bp 的图上实测:border=5pt 得到 67.05 × 38.71 bp(每边 5 pt,约 4.98 bp),border={10pt 5pt} 得到 77.02 × 38.71 bp。由于 border 和 varwidth 不是全局设定,可以稍后用 \standaloneconfig{...} 修改——在导言区,或在启用 multi 时甚至在正文中途。
| 选项 | 作用 | 默认值 |
|---|---|---|
crop | 把内容装入盒子,并把页面裁到内容大小加边距 | true,由 standalone.cfg 设定 |
preview | 通过 preview 宏包(带 active、tightpage)裁切的旧方法;与 crop 互斥 | off |
border / margin | 裁切时加上的边距:1 个值=四边,2 个=水平/垂直,4 个=左/下/右/上 | 0pt |
varwidth | 用 varwidth 环境包住内容,让段落取自然宽度;varwidth=6cm 给出上限 | off |
tikz / pstricks | 加载绘图宏包,并把它的每个环境各裁成一页(设置 multi=tikzpicture、varwidth=false) | off |
multi / ignorerest | 允许多页内容,每页分别裁切;ignorerest 会丢弃已声明环境之外的内容 | off |
class | 选择底层加载的类;也可以指定日文类,如 class=jsarticle | article |
beamer | 关闭裁切,改把内容排在一张空白 beamer 帧上 | off |
从一个文件切出多张图(multi)
默认情况下,document 环境里的全部内容会成为一页。启用 multi 后,指定的环境每出现一次就会被切成单独一页,并各自裁切。\documentclass[tikz]{standalone} 之所以方便正是因为这个:tikz 选项在内部设置了 multi=tikzpicture 和 varwidth=false,所以连写两个 tikzpicture,PDF 就有两页。(PSTricks 有对应的 pstricks 选项。)要针对自定义环境,就声明 \standaloneenv{myfig} 并且不要在环境之外放任何东西;只有确实必须在中间写内容时,才加上 ignorerest。此外还有 math 选项,可把公式逐个切出,它会同时设置 multi、ignoreempty 和 0.50001bp 的边距。
使用 standalone 时常见的错误与症状
standalone 的错误原因都很明确,可以从症状一一倒推。最常见的是把 figure 环境放进 standalone 文件里:在 crop 或 preview 生效时会出现 ! LaTeX Error: Not in outer par mode. 或「Float(s) lost」。因为裁切是靠把内容装进盒子实现的,而盒子里浮动体浮不起来。由于 crop 与 preview 都会自动设置 float=false,这个错误只会在 你事后手动写了 float=true 时出现。请把浮动体放在主文档,standalone 文件里只留图本身。
- 图的右侧出现大片空白。 内容变成了段落。删掉
\end{document}前的空行或多余的\par,或加上varwidth,或用multi与\standaloneenv声明环境。 - 右边被裁掉、内容缺了一块。
varwidth的上限(默认是\linewidth)太窄。可以像varwidth=15cm那样放宽,或用varwidth=false关掉。 - 选项的值被拒绝。 在布尔键上写了
true/false以外的值,会以! Class standalone Error: Invalid value 'maybe' for boolean key 'crop'.这样的形式停下。 - 多页文件里混进了多余的页。 你用了
multi,而在已声明的环境之外还有会被排版的内容。删掉它,或启用ignorerest。 - 走 DVI 路线时裁切不正常。
crop在 DVI 模式下写出的是 PostScript 命令,手册自己就说明这部分代码是实验性的。走latex时,preview有时更稳。
宏包一侧——从主文档 \input
在主文档导言区 尽量早 加载 \usepackage{standalone},宏包就会重新定义 \documentclass,使被 \input 的图文件中从 \documentclass 到 \begin{document} 的部分被 跳过。图文件的 document 环境被当作普通 TeX 分组处理,\end{document} 之后的内容也会被忽略;于是只有图文件的内容流入正文。前提只有一条:图文件需要的宏包必须由主文档加载。 因为图文件的导言区会被跳过,tikz 之类只能由主文档来读。
\documentclass{article}
% load the standalone package early
\usepackage{standalone}
% and everything the sub-files need
\usepackage{tikz}
\begin{document}
\begin{figure}
\input{figure}% the standalone file from above
\caption{A sub-file}
\end{figure}
\end{document}如果嫌手工抄写导言区麻烦,\usepackage[subpreambles=true]{standalone} 会替你收集:各图文件的导言区会汇总到辅助文件,下一次处理时并入主文档。再加 sort,各图加载的宏包及其选项会去重整理,并通过 \PassOptionsToPackage 加载,从而避免选项冲突。若想自己抄进主导言区,可用 print 输出清单——但这是纯收集模式,正如提示 Package standalone Warning: Running 'standalone' package in sub-preamble print mode. All body content of file 'figure.tex' is ignored! 所言,正文不会被排版。
\includestandalone 与 mode=:用源码还是用图像
把 \input 换成 \includestandalone{figure},就能用宏包选项 mode= 决定图以何种方式进入。取值有 tex(包含源码,默认)、image(用 \includegraphics 包含已有的 PDF 或 EPS)、image|tex(有图像就用图像,否则用源码)、build(每次都先构建)、buildmissing(只在图像不存在时构建)、buildnew(只在源码更新时构建;XeLaTeX 下不可用)。目的在于速度:复杂的图不必在主文档每次编译时都重排。只有三个 build 模式会调用外部命令,那时需要 -shell-escape。手册明确写明:构建失败时会发出警告,并改为包含源码。
再往后的工序——把裁好的 PDF 导出为 PNG 或 SVG、用 pdfcrop 去掉既有 PDF 的白边、dvisvgm 的用法、交给 convert= 的设置——由「导出为图像」那一页负责。就 standalone 的类而言,需要记住的只有一点:一开始就不产生边距,比事后再裁掉更快也更准确。
在项目中的组织方式
在真实的论文里,把每个 standalone 文件当作 图的源文件,并保持它可以脱离正文单独检查,会很有好处。主文件叫 paper.tex,图放在 figures/ 下,正文只写 \input{figures/energy-flow}。这样,修改图的人只编译 figures/energy-flow.tex 就能确认,而主文档只管理标题、编号和引用。审阅时附上这份单页 PDF,安排一次「只看图」的检查,就能不等整篇稿件重建而提升图的质量。
paper.tex
standalone.cfg # optional: your own defaults for every figure
figures/
energy-flow.tex
apparatus-layout.tex
timing-diagram.tex当图的内容应当遵循与正文相同的排版规则时,用 class= 让底层类保持一致。带日文标签的图,在 upLaTeX 下写 \documentclass[class=jsarticle,border=5pt]{standalone},在 LuaLaTeX 下写 class=ltjsarticle,日文字距和字体就能与正文对齐。类和宏包共同提供的 \ifstandalone、\IfStandalone{单独时}{被包含时}、\onlyifstandalone{...} 也很方便:只在图文件里显示比例尺或调试用边框,一行即可。
与 subfiles、TikZ external 的区别
有两个目的相近的机制,方向都 正好相反。在 subfiles 中,是子文件导入主文档的导言区;standalone 则反过来,可以把子文件的导言区收集进主文档。因此,把一张图在论文、演讲、学位论文等 多个文档中复用,适合用 standalone;而按章拆分、主文件与子文件一一对应的场景,适合 subfiles。TikZ 的 external 库 是从主文件写出临时图像,方向同样相反。不过,用 \includestandalone[mode=buildnew] 也能得到基本相同的「缓存重绘图」效果,同时把图保持为一个自成一体的文件。