彩色表格 (colortbl)

为 LaTeX 表格着色的 colortbl 宏包,并不是在给单元格的背景上色。它是在单元格 背后铺上彩色面板——作者 David Carlisle 在自己的文档里就是这样写的。表格着色中几乎所有令人费解的现象,都可以从这一句话推导出来:为什么明明写了 \cline,线却看不见?为什么与 booktabs 搭配时,色块与线条之间会留下一道白带?本页从 \usepackage[table]{xcolor} 这一行开始,依次介绍 \rowcolor(行)、\columncolor(列)、\cellcolor(单元格)、做斑马纹的 \rowcolors 和给线条着色的 \arrayrulecolor,最后还要听听 booktabs 那一方的说法:也许根本就不该用颜色。

给表格着色需要哪个宏包

需要的只有一行:\usepackage[table]{xcolor}。没有必要显式载入 colortbl——xcolortable 选项 会自动把它带进来。反过来只写 \usepackage{colortbl} 也可以,但这样就缺少颜色 名称colortbl 内部依赖 arraycolor,而 color 几乎不自带颜色名,于是写下 \rowcolors{2}{gray}{white} 的瞬间就会以 ! LaTeX Error: Undefined color 'gray'. 中断。提供颜色名与混色正是 xcolor 的职责,所以实践中大家都落回带 [table] 的那一行。

preamble
% one line brings in both xcolor and colortbl
\usepackage[table]{xcolor}

只有 beamer 的写法不同:需要作为类选项传入,即 \documentclass[xcolor=table]{beamer}。颜色本身可以直接使用 xcolor 的表达式,所以除了 grayred 这类名称外,还可以写 gray!20(20% 浓度)或 red!30!yellow(30% 红混入黄)。在文字背后铺饱和色会让文字读不清,因此表格底色的实用区间大约是 name!15name!25 这样的淡色。

Option clash for package xcolor — 以及它为何几乎不再出现

! LaTeX Error: Option clash for package xcolor. 曾经是表格着色中最常踩到的地雷。原因很简单:tikzpgfplotsbeamer 已经先载入了 不带选项的 xcolor,于是后写的 \usepackage[table]{xcolor} 就被判定为「用不同选项重复载入同一个宏包」。但是 xcolor 3.00(2023 年 11 月 11 日) 改变了这一点。从该版本起,table 不再是载入期选项,而是通过 LaTeX 新的键/值机制声明的 导言区键,因此可以对已经载入的 xcolor 事后生效。在 TeX Live 2024 上先载入 tikz 再写 \usepackage[table]{xcolor},完全不会报错——而对 geometry 做同样的实验,仍旧会停在 Option clash for package geometry.

也就是说,网上流传的「把 [table] 提到类选项里」这条经典建议,如今只是针对旧环境的应急办法。但旧的 TeX Live 和发行版仍会遇到,所以记住处理顺序仍有价值。先移到类选项:写成 \documentclass[table]{article},你的要求就排在了所有人之前。若仍不行,就 直接载入 \usepackage{colortbl}——冲突的是 xcolor 的选项,而不是 colortbl 本身。另外,xcolor 3.00 的新机制只在 LaTeX 2022-06-01 及更新版本 下生效;面对更旧的格式会自动改用冻结版本。反过来,如果新行为破坏了既有文档,写 \usepackage{xcolor}[=v2] 就能明确地把旧版本召回。

\rowcolor\columncolor\cellcolor 的区别与放置位置

这三个命令不只是着色范围不同,书写位置也各不相同\rowcolor{color} 放在 行首,即该行第一个单元格内容之前,也就是紧接上一个 \\ 之后。\columncolor{color} 不写在表格主体里,而写在 列指定内部,形式为 >{\columncolor{gray!20}}>{…}array 宏包提供的钩子,会在该列每个单元格之前插入内容,colortbl 正是搭了它的便车。只有 \cellcolor{color} 不受限制:写在目标单元格内的 任意位置 都有效。这种不对称不好记,但道理很清楚——行只能在它开始的那一刻抓住,列只能在表格开始之前声明。

三者的引数形式一致:\columncolor[model]{color}[left overhang][right overhang],颜色名前可加 颜色模型 可选引数,其后可加 左右伸出量(overhang) 可选引数。伸出量指定色块向列的左右各延伸多远。省略右侧时会沿用左侧的值;两者都省略时,tabular 中用 \tabcolseparray 中用 \arraycolsep。若把两者都设为 0pt,色块会正好在列内容的宽度处截断——单元格之间出现白缝通常就是这个原因,所以想让相邻颜色连成一片时,应当加大而不是减小伸出量。

latex
\begin{tabular}{>{\columncolor{gray!20}}l c r}
  \rowcolor{blue!30}
  Item & Qty & Price \\
  Apple  & 3 & 380 \\
  Orange & \cellcolor{yellow!40}5 & 120 \\
\end{tabular}

这三件套并非一次设计成型,而是花了二十年陆续添上的。colortbl 的初稿写于 1996 年 9 月 20 日,当时只有 \columncolor\rowcolor 在半个月后的 1996 年 10 月 5 日加入,变更记录写明是「为了让 S. Rahtz 高兴」——即 TeX 世界的重要人物 Sebastian Rahtz。而 \cellcolor 直到五年后的 2001 年 2 月 13 日才由 Donald Arseneau 贡献进来。在那之前,要给单个单元格着色只能写 \multicolumn{1}{>{\columncolor{…}}c}{…},Carlisle 列出了这种写法的三条缺点:单元格内容可能妨碍着色触发;列对齐必须手工照抄,在 p{} 列中尤其容易出错;第三条则是 \multicolumn{1} 本身就很荒唐。要给一个单元格着色,直接用 \cellcolor

颜色重叠时哪个胜出:单元格>行>列

\cellcolor 覆盖 \rowcolor\rowcolor 覆盖 \columncolor——即 单元格>行>列。范围越窄,主张越强,这正合直觉,无需重新记忆。有了这个次序,就可以放心地叠三层:整列铺浅灰,仅表头行染蓝,再把想让人注意的那一个单元格染黄,完全不必担心冲突。从设计上理解,可以把范围大的声明看作「默认值」,把范围小的看作对它的「例外」。

命令着色对象放置位置
\columncolor{c}整列列指定中的 >{…}
\rowcolor{c}整行行首,第一个单元格之前
\cellcolor{c}单个单元格该单元格内任意位置
\rowcolors{n}{a}{b}交替行(斑马纹)表格之前一次
\arrayrulecolor{c}之后的所有线条表外、行首,或 >{}
\doublerulesepcolor{c}双线之间的间隙\arrayrulecolor 相同

\rowcolors 自动生成隔行斑马纹

要让表格隔行变色,只需在表格 之前 写一次 \rowcolors{start}{odd-row color}{even-row color} 即可。末尾的 s 很关键:没有 s\rowcolor 是另一个只给单行着色的命令。第一个引数是开始着色的行号,所以想让表头行原样通过就传 2。两个颜色引数都可以留空,空表示「无颜色」。这里有一点与搜索得到的旧文章不符:\rowcolors 已经不再是 xcolor 的命令了。 相关代码在 2022 年 6 月 20 日发布的 colortbl 1.0f 中从 xcolor 移入 colortbl,xcolor 3.00 的变更记录也明确写下了这次移交。使用者的写法没有变化,但知道如今仅靠 \usepackage{colortbl} 就已经定义了 \rowcolors,仍然有用。

latex
% start striping at row 2, so the header stays plain
\rowcolors{2}{gray!15}{white}
\begin{tabular}{l r r}
  Item & Qty & Price \\
  Apple  & 3 & 380 \\
  Orange & 5 & 120 \\
  Grape  & 2 & 600 \\
  Peach  & 4 & 450 \\
\end{tabular}

斑马纹并非「全有或全无」。首先,\rowcolors 前面可带一个可选引数:写成 \rowcolors[\hline]{2}{…}{…} 就会在每行之前执行 \hline 之类的命令。其次,显式的 \rowcolor\multicolumn 优先级高于 自动斑马纹,因此在斑马纹中途把某一行改成别的颜色很自然。再者,为了临时关闭斑马纹,还有 \hiderowcolors\showrowcolors,把它们放在表格中间,即可从该处起取消或恢复这一图案。「除合计行外全部加斑马纹」这类要求就靠它们解决。

给线条着色:\arrayrulecolor\doublerulesepcolor

\hline\cline 和竖线 | 的颜色用 \arrayrulecolor{color} 更改。它接受与 \color 相同的引数,是一个 全局声明,可放在表格之外、行首,或列指定的 >{} 中。这里有一个陷阱:若在表格中途指定,只对该点之后指定的线条生效,列指定阶段已经声明的竖线仍保持原色。由于是全局声明,它在表格结束后依然有效,所以若不想牵连之后的表格,就要再次声明 \arrayrulecolor{black} 以恢复默认值。

||\hline\hline 形成的 双线之间的间隙 默认保持白色。要填上它,需要结合决定间隙宽度的长度 \doublerulesep 使用 \doublerulesepcolor{color}。但要注意,一旦用了这个命令,longtable 在分页处就 无法再丢弃 \hline\hline 之间的空白了。TeX 确实有丢弃空白(glue)的机制,但着了色的「空白」实际上是第三条线,而线条远比空白难以丢弃。如果长表格将要跨页,这一点值得提前知道。

latex
\setlength\arrayrulewidth{1pt}\arrayrulecolor{blue}
\setlength\doublerulesep{2pt}\doublerulesepcolor{yellow}
\begin{tabular}{||l|c||}
  \hline\hline
  one & two \\
  three & four \\
  \hline\hline
\end{tabular}
\arrayrulecolor{black} % global: reset, or later tables inherit it

若只想给某一条竖线着色,可以完全不用专门命令,而在列指定中用 !{\color{green}\vline} 代替 |。还有一个虽小却实用的点:\arrayrulewidth 是决定线条粗细的长度,保持默认的细线再着色,线会显得非常弱。原本用黑色时足够的粗细,一换成浅色就可能从纸面上消失——所以不妨记住:着色之后就把线稍微加粗一点。

为什么 \cline 会消失在着色行之下

在使用了 \rowcolor 的表格里画 \cline,线会 完全看不见。这不是缺陷,而是开头提到的那套机制的必然结果。colortbl 把颜色 作为面板铺在单元格背后,而 \cline 画得比这些面板还要靠后,于是被颜色盖住。Carlisle 在源码注释中承认,\cline 在着色表格中实际上不起作用,只能退而求其次,让还看得见的那部分至少颜色正确。规避办法很明确:不用 \cline,改为载入 hhline 宏包,写成 \hhline{-~} 这样的形式,其中 - 表示该列画线,~ 表示跳过。这样线就画在面板之上,能正常显示。

latex
% \cline{1-1} would vanish behind the panel; \hhline draws on top
\usepackage{hhline}
...
\begin{tabular}{ll}
  \rowcolor{blue!40} one & two \\
  \hhline{-~}
  \rowcolor{blue!40} three & four \\
\end{tabular}

与 booktabs 并用时,线条与背景色之间的白色缝隙

colortblbooktabs 可以毫无问题地并用,但在 \toprule\midrule\bottomrule 与背景色之间会留下一道 白带。原因是 booktabs 在线条上下插入的空白,其默认值为 \aboverulesep 等于 .4ex\belowrulesep 等于 .65ex。这段空白位于行盒之外,因此 \rowcolor 的面板够不到。若希望填色一直触及线条,标准做法是把这两个空白设为 0pt,再把失去的高度转移到 \extrarowheight 中:行高保持不变,只有颜色变化,此时它正好与线条相接。

latex
% let the row color reach the booktabs rules
\setlength\aboverulesep{0pt}
\setlength\belowrulesep{0pt}
\setlength\extrarowheight{.75ex}

另一个常见症状是:大字号或较高的内容会碰到紧邻其上的 \hline 或色块顶端。由于 colortbl 本来就要把每个单元格装箱测量,以便算出线条该有多宽,于是顺手加入了长度 \minrowclearance。设置 \setlength\minrowclearance{2pt} 之类的值,只会在较高的行上方加入一点空白。不过 Carlisle 本人写道,更好的做法是先增大 \extrarowheight\arraystretch,因为它们能在保持行距均匀的前提下解决问题——一旦大写字母的高度加上这个值超过普通行高,行距的参差就会肉眼可见。

最后留一个问题:这里到底该不该用颜色?写出 booktabsSimon Fear 立场十分明确:好的表格靠 留白和少数几条横线 让人读懂;守住两条准则——绝不使用竖线,绝不使用双线——就不会偏差太远。他把「formal table(正式表格)」,即带标签列的一组数值,与很可能满是图标、也会用颜色的「tableau」区分开来,并指出前者的版式是数百年经验确立的,除非情况极其特殊,否则不应改动。顺带一提,booktabs 的版本号是 1.61803398——作者的玩笑,说它正收敛于黄金比例 φ。给表格铺色之前,值得先问一句:单靠行高和两三条线,是不是就已经解决了?