在 LaTeX 里想给文字加个边框,多数人第一个敲下的是 \fbox{...}。它确实有效——只要内容不超过一行。一旦把整段文字交给它,边框不会换行,会一直伸到纸张外面,日志里出现 Overfull \hbox。正是这一个限制,催生了 framed、mdframed、tcolorbox 这一整套加框宏包。本页从内建的 \fbox 与 \framebox 讲到可跨页的边框,再到带颜色和标题的面板,贯穿其中的只有一个问题:边框为什么不能被切成两半?
\fbox 与 \framebox:内建边框,以及它的极限
\fbox{...} 不需要加载任何宏包,会紧贴参数内容画一道细线。若要指定宽度和对齐,就用 \framebox[width][position]{...}:第一个可选参数决定盒子宽度,第二个决定内容在盒子里的位置——c(居中,默认)、l(左对齐)、r(右对齐)、s(拉伸到两端)。不带可选参数的 \framebox 与 \fbox 行为完全一致。两者都是行内盒子,落在基线上,因此能与 \hspace 和前后文字自然排在一起。
\fbox{a framed word}
% width 5cm, content flush left inside the box
\framebox[5cm][l]{flush left}
% multi-line: frame a \parbox, not the text itself
\fbox{\parbox{0.8\linewidth}{%
Several lines of text. The inner box wraps them
first, and only then does the frame go around.}}接下来就是那堵墙。\fbox 和 \framebox 都不会让内容换行。给它一段长文字,它就在同一行上不断向右生长,把边框推出纸面,并给出 Overfull \hbox (... too wide) 警告。这不是 bug,而是定义本身:\fbox 会把参数装进一个水平盒子(\hbox),而 \hbox 内部根本不存在断行点。要框住多行,就像上面那样先用 \parbox 或 minipage 把段落排好,再去框那个盒子。\parbox 与 minipage 本身见“盒子”页面。不过这一招在边框必须跨页时同样失效——原因马上就讲。
\fboxrule 与 \fboxsep:线更粗,内边距更宽
边框的样子只由两个长度决定。\fboxrule 是线条粗细,标准文档类的默认值是 0.4pt;\fboxsep 是边框与内容之间的内边距,默认 3pt。二者都用 \setlength 修改。默认的 0.4pt 细得跟正文差不多,想让方框作为提示醒目起来时通常不够。不过要注意,它们是普通的全局长度:在导言区改动,全文的每个 \fbox 都会跟着变。只想加粗某一个框,就把 \setlength 放进花括号分组 {...} 里,如下所示。加载 color 或 xcolor 后还能用 \fcolorbox{框线色}{背景色}{...},它遵循的正是同样这两个长度——颜色名与混色见“颜色(xcolor)”页面。
% global: every \fbox in the document becomes thicker
\setlength{\fboxrule}{1pt}
\setlength{\fboxsep}{8pt}
% local: the group limits the change to this one box
{\setlength{\fboxrule}{2pt}\fbox{a heavy frame}}
% same two lengths, plus colour (needs color or xcolor)
\fcolorbox{red}{yellow!20}{warning}为什么加框的内容不能跨页断开
答案很简单:被画上边线的材料会变成一个盒子,而 TeX 的分页器只能在竖直列表的元素“之间”断开,绝不会切进盒子内部。一旦把内容围起来,它就从可分页的位置列表中消失了。这也揭示了“可跨页边框”的真相:从来没有哪一个边框真被切成两半。宏包先把内容切成适合页面的若干块,为每一块单独画一个边框,再把它们叠起来,让接缝看不出来。
Donald Arseneau 的 framed 宏包干脆把这套机制摆在设计表面。其文档写明:被拆开的环境,第一块用 \FirstFrameCommand,中间各块用 \MidFrameCommand,最后一块用 \LastFrameCommand。这正是它的 oframed 环境能在分页处上下开口的原因——两端和接缝用不同的命令,被切开的地方就不画横线。framed.sty 的版权声明从 1992 年算起,说明这个问题比 LaTeX2e 本身还要老。既然机制是“先拆开、再装饰”,限制也就顺理成章:可跨页边框内部的脚注、浮动体和跨栏材料都很脆弱,tcolorbox 和 mdframed 的手册都把这一点列为注意事项。
framed 宏包:framed、shaded、leftbar 三个环境
只写一行 \usepackage{framed},就能得到三个可以跨页的环境。framed 画出与正文同宽的边框,shaded 不画线而是填充背景,leftbar 在左边距画一条粗竖线,常用来标记引文和旁注。使用 shaded 系列前必须先定义名为 shadecolor 的颜色:可以直接写 \definecolor{shadecolor}{gray}{0.9},也可以加载 xcolor 后写 \colorlet{shadecolor}{gray!15}。忘了定义,编译就会以“颜色未定义”的错误停下,而不是给出什么友好提示。
\usepackage{framed}
\usepackage{xcolor}
\colorlet{shadecolor}{gray!15}
% these lengths are the framed counterparts of
% \fboxrule and \fboxsep
\setlength{\FrameRule}{1pt}
\setlength{\FrameSep}{10pt}
\begin{framed}
A long note. If it reaches the bottom of the page the
frame is split and continues on the next one, which is
precisely what \fbox cannot do.
\end{framed}
\begin{shaded}
Filled background instead of a rule.
\end{shaded}
\begin{leftbar}
A single vertical rule in the left margin.
\end{leftbar}尺寸用 \FrameRule 和 \FrameSep 调整——宏包文档明确说明,这两个长度就是在 framed 环境内部代替 \fboxrule 和 \fboxsep 的。环境前后的竖直间距是 \OuterFrameSep,默认等于 \topsep。它还带了几个派生环境:shaded* 让填充的边缘与正文宽度对齐(不带星号的 shaded 会稍稍溢入页边距),snugshade 和 snugshade* 紧贴内容,oframed 就是上一节说的在断开处开口的边框。再往前一步,自己定义 \FrameCommand 并用 \MakeFramed ... \endMakeFramed 包住材料,就能造出任意可跨页的装饰——文档称这才是本宏包更一般的用途。framed 不提供的是圆角、阴影和标题。需要这些,就该升一级了。
用 \boxed 框住公式——它骨子里就是 \fbox
要给结论公式加框,加载 \usepackage{amsmath} 并在数学模式中使用 \boxed{...}:它会先把参数当作数学排版,再加上边框。有意思的是它的定义——在 amsmath 里,\boxed 基本就写成 \fbox{\m@th$\displaystyle#1$}。它根本不是另一套绘制机制,只是把公式包进 $...$ 再交给 \fbox。由此有三个实用推论:\fboxrule 和 \fboxsep 对它同样有效;它当然不能跨页;而且因为强制了 \displaystyle,写在下标里的 \boxed 会以完整尺寸出现,周围的东西却缩小了。
\usepackage{amsmath}
\[
\boxed{E = mc^2}
\]
% \fbox inside math sets its argument in TEXT mode:
% upright roman letters, not math italic
$\fbox{x+y}$ \quad versus \quad $\boxed{x+y}$把两者并排一看,差别就很明显。$\fbox{x+y}$ 把参数按文字模式排版,于是 x 和 y 都成了正体罗马字,+ 前后的间距也完全不是数学间距。$\boxed{x+y}$ 则保留数学斜体和数学间距。在数学里需要边框,就毫不犹豫地用 \boxed。若要求更复杂——连公式编号一起框住,或跨越 align 的多行——随 mathtools 提供的 empheq 宏包才是合适的工具。数学模式本身见“数学模式基础”页面。
tcolorbox:颜色、标题与 breakable
想一次解决颜色、标题和分页,答案就是 tcolorbox。用 \usepackage[most]{tcolorbox} 加载,然后给 tcolorbox 环境传入 colback=(背景色)、colframe=(框线颜色)、title=(标题条)和 breakable(允许跨页)。最容易踩的坑是 most 到底管什么。tcolorbox 由内核加上一大批 library 组成,而 breakable 既是一个选项名,也是一个 library 的名字。只写 \usepackage{tcolorbox} 再加 breakable,框仍然不会断开,因为那个 library 根本没被加载;必须写 [most],或显式写 \tcbuselibrary{breakable}。查看源码可知,most 展开为 many(即 raster、skins、breakable、hooks、theorems、fitting)再加 listingsutf8、external、magazine、vignette、poster——除了依赖外部工具的 minted 之外,全都在内。
\usepackage[most]{tcolorbox}
\begin{tcolorbox}[colback=blue!5, colframe=blue!60!black,
title=Remarks, fonttitle=\bfseries, breakable]
A coloured box with a title bar. Because breakable was
given, a long body is split across the page boundary.
\end{tcolorbox}
% inline, fitted to its content, no page breaks
\tcbox[colback=red!5, colframe=red!60!black]{inline box}形状用数值键调整:boxrule= 是框线粗细(默认 0.5mm),arc= 是圆角半径(默认 1mm);要直角就写 sharp corners,要默认的圆角就写 rounded corners。标题条背景用 colbacktitle=,标题字体用 fonttitle=(传入 \bfseries 之类)。若只想在一行之内放一个小框,就用 \tcbox{...} 而不是环境——它像 \fbox 一样贴合内容,也像 \fbox 一样不会断开。底层是 pgf:内核依赖 pgf,而 most 中包含的 skins library 直接依赖 tikz。所以指定 enhanced 就把 TikZ 的绘图能力摆到台面上——阴影、双重边框、透明和装饰标题。还有用于定理式方框的 theorems library、用 \tcblower 把框上下切成两半从而让源码位于输出之上的两段模式,以及远不止于此的功能。
“远不止于此”不是修辞。TeX Live 2024 附带的 tcolorbox 手册(用 texdoc tcolorbox 打开)足有 548 页——对于一个画方框的宏包来说,这已经是一本小型专著了。作者是 Thomas F. Sturm,他还写了从 LaTeX 读取 CSV 文件的 csvsimple 和排版家谱的 genealogytree。实用建议是:别想着通读那 548 页。colback、colframe、title、breakable 这四个键几乎能覆盖大多数文档的需要;真想玩点花样时,再去查手册的索引。
用 \newtcolorbox 复用同一种样式
在正文里一遍遍重复同一组选项,等到哪天想换颜色就一定会后悔。请在导言区用 \newtcolorbox 只定义一次。格式是 \newtcolorbox[init options]{name}[参数个数][默认值]{options},和 \newenvironment 一样可以创建带参数的环境。想要命令而非环境,就用 \newtcbox;想一次性改变全文默认值,就用 \tcbset{...}。有个习惯值得养成:按 含义 命名,如 note、warning、definition,而不是按外观命名成 blueroundbox 之类。这样以后要改配色,只需动导言区的几行,而不必在正文里全局替换。
\usepackage[most]{tcolorbox}
% one argument: the heading text
\newtcolorbox{note}[1]{%
colback=yellow!10, colframe=orange!70!black,
fonttitle=\bfseries, title=#1, breakable}
\begin{document}
\begin{note}{Careful}
The note environment is now defined; its heading
arrives as an argument.
\end{note}
\end{document}mdframed:介于 framed 与 tcolorbox 之间
想要一个比 framed 好看、又不像 tcolorbox 那么重的可跨页边框,mdframed 正好落在这个位置。用 \usepackage{mdframed} 加载,再给 mdframed 环境的可选参数传入 backgroundcolor=、roundcorner=、linecolor=、linewidth= 之类的键。这个宏包为什么被写出来,作者在源码开头就用自己的话说明了:使用 \fbox 或 \fcolorbox 时必须手工处理分页,而 mdframed 环境会自动处理。本页开头那个 \fbox 的限制,就这样白纸黑字成了另一个宏包的存在理由;源码同样明确写着,其实现建立在 framed.sty 的思路之上。
\usepackage{mdframed}
\begin{mdframed}[backgroundcolor=gray!10, roundcorner=5pt,
linecolor=gray!60, linewidth=1pt]
An mdframed box. It breaks across pages.
\end{mdframed}
% a named environment of your own, and a framed theorem
\newmdenv[linecolor=red, linewidth=1.5pt]{alert}
\newmdtheoremenv{prop}{Proposition}它的特色是可以选择绘制后端。framemethod=default 用普通 LaTeX 命令绘制,改成 framemethod=TikZ 或 framemethod=pstricks 则能启用更精致的装饰。常用设置可以集中写进 \mdfsetup{...}。自定义的加框环境用 \newmdenv{...} 定义,带边框的定理环境用 \newmdtheoremenv{...}。有一点值得知道:mdframed 的当前版本是 1.9b,日期是 2013 年 7 月 1 日——源码里至今还留着一句“本宏包目前处于 beta 状态”。尽管如此,它已经在 TeX Live 里待了十多年,并且确实能用。不过,如果新文档要做大量定制,仍在活跃维护的 tcolorbox 是更稳妥的选择。
该用 \fbox 还是 tcolorbox:一张速查表
判断标准其实只有一个:要围起来的材料有多大。一个词或一行,用 \fbox / \framebox;一个公式,用 \boxed;给一组段落加个朴素的框,用 framed;要颜色和圆角,用 mdframed;要标题、分页和装饰,用 tcolorbox。拿不准时,先问自己方框是否必须能跨页——单凭这一问,候选项就干净地分成上下两组。还有一条实用建议:framed 与 tcolorbox 在同一文档里共存并不冲突,但若在意整体观感的一致,专心用其中一个效果更好。
| 命令/宏包 | 可框住的内容 | 可跨页? | 适合场景 |
|---|---|---|---|
\fbox / \framebox | 能放进一行的内容 | 否 | 快速框住一个词或一句短话 |
\boxed | 数学内容(数学模式) | 否 | 突出显示结论公式 |
framed | 段落块 | 是 | 不加装饰的轻量边框 |
mdframed | 段落块 | 是 | 以较小代价获得颜色和圆角 |
tcolorbox | 段落、行内、定理环境 | 是(breakable) | 标题、配色与装饰一应俱全 |