graphicx 常被当作插图宏包,但名字里的 x 意为「扩展」,而不是「只管图像」。它的 \rotatebox、\scalebox、\resizebox、\reflectbox 可以旋转、拉伸、镜像 任何能装进盒子的东西——一个词、一个公式,乃至整个 tabular 表格。不过盒子的尺寸并不会按直觉变化。实测显示,把 10pt 的 Xg 用 \rotatebox{-90} 向右倒下后,得到的盒子 高度为 0pt、深度为 12.50pt:整个内容都吊在基线之下。表格表头一竖起来行距就乱掉,原因正在于此。本页梳理 LaTeX 中 旋转与缩放文字 的命令,并说明两个最容易踩的坑:尺寸与驱动。
不只用于图像 —— graphicx 的四种盒子变形
只需 \usepackage{graphicx} 一行,四个变形命令就都能用了。它们分成两组:按倍率指定的(\scalebox、\reflectbox)、按目标尺寸指定的(\resizebox),以及按角度指定的 \rotatebox。\scalebox{0.8}{…} 表示「原来的 0.8 倍」,\resizebox{5cm}{!}{…} 表示「把宽度变成 5cm」。该用哪一个,取决于 你手上的数是比例还是尺寸。要让图表适应正文宽度就用后者;只想把某个词稍微放大一点就用前者。
| 命令 | 作用 | 写法 |
|---|---|---|
\rotatebox | 把内容逆时针旋转 | \rotatebox[origin=c]{45}{...};单位为度,负数表示顺时针 |
\scalebox | 按倍率缩放 | \scalebox{2}[1.5]{...};可选的第二个参数是纵向倍率 |
\reflectbox | 左右镜像 | \reflectbox{...};内部与 \scalebox{-1}[1] 完全相同 |
\resizebox | 缩放到指定的成品尺寸 | \resizebox{宽}{高}{...};把其中一个写成 ! 即保持比例 |
\rotatebox —— 单位为度,正值表示逆时针
\rotatebox{角度}{内容} 把内容装进盒子,并按给定的 角度(以度为单位)逆时针 旋转。正值向左转,负值向右转:\rotatebox{90}{...} 会把文字竖起来,\rotatebox{-90} 则朝另一边倒下。若想用别的单位书写角度,可用可选键 units=,它接受 对应一整圈的数值——units=-360 表示顺时针的度,units=6.283185 表示逆时针的弧度。如果每次都要想角度正负很麻烦,把它在导言区包装一次是最省事的办法。
This is \rotatebox{45}{slanted text} in a line of prose.
% clockwise degrees instead of the default anticlockwise:
\rotatebox[units=-360]{90}{tipped to the right}接下来谈尺寸,这是多数人翻车的地方。旋转后盒子的宽、高、深,是 按旋转结果的外形轮廓 重新计算的。以 10pt 的 Xg(宽 12.50pt、高 6.83pt、深 1.94pt)实测,\rotatebox{90} 得到 宽 8.78pt、高 12.50pt、深 0pt——原来的「高+深」变成了新的宽,原来的宽变成了新的高。\rotatebox{-90} 宽度相同,但 高 0pt、深 12.50pt,整个盒子沉到基线之下。至于 \rotatebox{45},宽 15.05pt、高 13.67pt,横竖都比原来更大。旋转会改变行高——知道这一点,就不会对加入旋转之后行距突然拉开感到意外了。
旋转轴 origin= —— 写下方括号的瞬间默认值就变了
旋转中心由可选参数 [origin=…] 指定。取值是 l(左)、r(右)、t(上)、b(下)、B(基线)、c(中心)中的一个或两个组合:origin=lb 以左下角为轴,origin=c 以盒子中心为轴。这里有一个 不知道就一定会犯迷糊的机关。翻开 graphicx.sty 会发现:不写 可选参数时,旋转中心是 (0,0),即左端且位于基线——等同于 origin=lB;而 只要写下任意一个键,默认值立刻切换为盒子中心 c。实测也印证了这一点:\rotatebox{90}{Xg} 与 \rotatebox[origin=lB]{90}{Xg} 完全一致,都是宽 8.78pt、高 12.50pt、深 0pt;而 \rotatebox[origin=c]{45} 同样是 45 度,却分成高 9.97pt、深 5.08pt,跨骑在基线两侧。「我只是加了个 units=,位置怎么就变了」,多半就是这个原因。
除 origin= 之外还有 x= 与 y=,它们 直接以长度 给出轴心位置。写 \rotatebox[x=1cm,y=1cm]{90}{...},旋转中心就落在从盒子左下角量起的 (1cm, 1cm) 处。当那几个关键字不足以表达细微调整时,就用这个工具。
% no optional argument: the pivot is the left edge on the baseline (= origin=lB)
\rotatebox{90}{upright}
% ANY key present: the default pivot becomes the box centre (origin=c)
\rotatebox[origin=c]{180}{upside down}
\rotatebox[x=1cm,y=1cm]{90}{pivot given as a length}把表头竖起来 —— 宽度是省了,行却往下沉
\rotatebox 最实用的用途,就是 把冗长的列名竖起来以省出宽度。效果确实明显:\begin{tabular}{ll}Header&b\\ c&d\end{tabular} 实测宽 60.42pt,把 Header 用 \rotatebox{90}{...} 立起来后缩到 36.50pt。但代价也在。同一次实测中,表格的深度从 9.50pt 增至 20.73pt。上一节的尺寸规律正在起作用:竖起来的表头会向行的下方延伸。放着不管,它就会与正文或下一行相撞。对策很直接——用 \rotatebox[origin=c]{90}{...} 把轴移到中心,用 \raisebox 把单元格抬起来,或者用 \rule{0pt}{高度} 这样的支撑撑出行高。至于把表格本身放上纸面的办法——横向页面、sidewaystable、声明宽度的列——那是表格相关页面的职责,请参阅那里。
\begin{tabular}{lrr}
% rotating the pivot to the centre keeps the row from sinking
& \rotatebox[origin=c]{90}{Measured} & \rotatebox[origin=c]{90}{Predicted} \\
\hline
Sample A & 12.4 & 12.1 \\
Sample B & 9.8 & 10.0 \\
\end{tabular}按倍率缩放与镜像 —— \scalebox 与 \reflectbox
\scalebox{倍率}{内容} 会把横竖按同一比例缩放,因而保持形状。\scalebox{2}{...} 放大一倍,\scalebox{0.5}{...} 缩小一半。若想让横竖伸缩不同,把 纵向倍率 作为可选的第二个参数传入,写成 \scalebox{横倍率}[纵倍率]{内容}。实测中 \scalebox{2} 把宽、高、深都变成两倍,而 \scalebox{1}[2] 保持宽度 12.50pt 不变,只把高和深加倍。这可以用来把字压扁或拉长,但要记住 字体的笔画粗细也会随之被单向扭曲,所以在正文中请谨慎使用。
倍率也可以取 负值,从而沿该轴翻转。\scalebox{-1}[1]{...} 是横向倍率 −1、纵向倍率 1,也就是左右镜像。这种情况很常见,于是有了专门的简写:在 graphics.sty 中,\reflectbox 被定义为 正是 \Gscale@box-1[1]。实测也一致:两者的盒子都是宽 12.50pt、高 6.83pt、深 1.94pt。可见 \reflectbox 不过是 \scalebox{-1}[1] 的别名。把回文 “Able was I ere I saw Elba” 的一半镜像过来,这个命令的行为便一目了然。
\scalebox{2}{bigger}\quad\scalebox{0.5}{smaller}\quad\scalebox{1}[2]{stretched}
% \reflectbox is literally \scalebox{-1}[1]
Able was I \reflectbox{Able was I}缩放到目标尺寸 —— \resizebox 与带星号的 \resizebox*
\resizebox{宽}{高}{内容} 把内容缩放到 指定的成品尺寸。若把其中一个写成 感叹号 !,那一维就沿用另一维算出的倍率——即保持纵横比。\resizebox{\textwidth}{!}{...} 是最常见的写法。在尺寸参数中可以使用 \width、\height、\totalheight、\depth,它们代表内容原本的大小。实测显示,\resizebox{2\width}{!}{Xg} 与 \scalebox{2}{Xg} 一字不差地相同:宽 25.00pt、高 13.67pt、深 3.89pt。
容易被忽略的一点是:\resizebox 的高度参数指的是「高」,而不是「高+深」。实测 \resizebox{!}{1cm}{Xg},高度为 28.46pt(约等于 1cm),但下方还吊着 8.10pt 的深度,整个盒子因此超过 1cm。若想按总高度指定,请使用 带星号的 \resizebox*。同样的 \resizebox*{!}{1cm}{Xg} 实测为高 22.15pt、深 6.30pt,合计 28.45pt——正好 1cm。当「明明缩到了 1cm 却还是溢出行外」时,请怀疑那个星号是不是漏了。
% twice the original width, aspect ratio kept
\resizebox{2\width}{!}{double width}
% height only vs. height+depth
\resizebox{!}{1cm}{Tall} % height = 1cm, depth hangs below
\resizebox*{!}{1cm}{Tall} % height + depth = 1cm\resizebox 最常见的用途,是 把超出正文宽度的表格整体缩小以放进去。把整个 tabular 用 \resizebox{\textwidth}{!}{...} 包起来,宽度必然合适。但这是一把双刃剑,因为 表格里的文字也会跟着缩小:缩得过狠时,字会比正文更小,有时难以辨认,也与周围字面不协调。事后再为表格重新指定字号也无济于事,因为最终整个盒子都会被缩放。表格究竟会缩多少,以及把过窄的表格交给同一命令时会被放大到何等地步,在宽表格那一页有带实测值的详细说明。正确的第一步是让表格本身适应宽度——调整列宽、删减列——而把 \resizebox 当作最后手段。
完全没有旋转 —— 驱动指定不匹配
\rotatebox 并不是让 TeX 亲自把字画成旋转的样子。TeX 只是在输出文件中埋入一条「把接下来的内容转多少度」的指令,真正执行旋转的是 后端的驱动程序。因此,一旦驱动指定错了,旋转就会在既无错误也无警告的情况下消失。来实测一下:把写有 \usepackage[dvips]{graphicx} 的文档用 pdfLaTeX 编译,过程会正常结束(零错误),但生成的 PDF 内容流中 一个变换矩阵 cm 都没有。文字就那样直挺挺地排着。把同一份文档的驱动选项去掉,日志会显示 Package graphics Info: Driver file: pdftex.def,而 PDF 中出现了 .70709 .70709 -.70709 .70709 0 0 cm——正是 45 度的余弦与正弦。
结论很简单:不要指定驱动。pdfLaTeX、XeLaTeX、LuaLaTeX 都会被 graphicx 自动识别,所以手写 [dvips] 或 [pdftex] 恰恰是在制造不匹配。只有走 DVI 路线时情况不同:用 latex 编译时,graphicx 的默认驱动是 dvips,DVI 里会写入原始 PostScript(ps: gsave currentpoint currentpoint translate 45 neg rotate …)。dvipdfmx 能在一定程度上解释这类 PostScript 特殊指令,所以实际上确实会旋转;但更顺畅的路径是明确写出 \usepackage[dvipdfmx]{graphicx},这时 DVI 中放入的是面向 PDF 的指令 pdf:btrans rotate 45。至于 DVI 究竟把什么交给驱动,这在讲 DVI 到 PDF 转换的那一页有详细说明。
更高层的 adjustbox —— max width= 只缩不放
graphicx 的命令简单而强大,但组合起来嵌套会很深。用 \usepackage{adjustbox} 载入的 adjustbox,是把这些功能统一为 \includegraphics 风格 键=值接口 的更高层替代。像 \adjustbox{angle=45,scale=0.8}{…} 这样,可以一次指定旋转(angle=)、倍率(scale=)、目标尺寸(width= / height=)与镜像(reflect),并且还有同名的 adjustbox 环境。实测中 \adjustbox{angle=45}{Xg} 的盒子与 \rotatebox{45}{Xg} 分毫不差,可见底层做的是同一件事。差别在于书写的便利,以及下面要讲的「上限」这一思路。
真正好用的键是 max width=\textwidth(以及 max totalheight= 之类)。上一节的 \resizebox{\textwidth}{!}{...} 即便内容比正文更窄,也会毫不客气地 放大;而 max width= 的设计是「只在溢出时缩小,已经放得下就不动」。实测显示:一个只有一列、宽 17.00pt 的小 tabular,经过 max width=\textwidth 之后 仍是 17.00pt;而把同一个表交给 \resizebox{\textwidth}{!},它被拉伸到了整整 345.00pt。所以,若按 \includegraphics 的习惯给所有表格都加上 \resizebox,变成怪物的恰恰是那些窄表。把「需要时才缩小、绝不放大」设为默认 ——在实务中这是事故最少的选择。
\usepackage{adjustbox}
% ...
\adjustbox{angle=45,scale=0.8}{rotate and scale in one go}
% shrinks only when the content overflows; leaves narrow tables alone
\adjustbox{max width=\textwidth}{%
\begin{tabular}{lrr} ... \end{tabular}%
}