TeX 里根本没有颜色这个概念。高德纳设计的排版引擎只用盒子和粘连把页面拼起来,那个模型中没有颜色的位置,也不存在表示颜色的原语。那么 LaTeX 的 xcolor 宏包到底在做什么?它给输出驱动留了一张便条。写下 \textcolor{red}{…},TeX 就在输出文件里夹进一条 \special——「从这里开始是红色」——真正上色的是后面生成 PDF 的那一方。知道了这一点,LaTeX 中几乎所有关于颜色的怪现象都能讲通。本页依次走完整条路径:加载 \usepackage{xcolor},接着是 \textcolor、\colorbox 和 \pagecolor,然后是 \definecolor 与颜色模型,再到 red!30!white 这样的混色记法,最后是那个收尾的陷阱——屏幕上的颜色,并不是印刷机上出来的颜色。
\usepackage{xcolor} 与 color 宏包的区别:该加载哪一个
答案永远是 \usepackage{xcolor}。它是 David Carlisle 的 color 宏包的上位兼容版:完整继承了 color 的全部命令,又在此之上加了混色、额外的色名集合和模型转换。作者是 Uwe Kern(2003–2021),2021 年之后由 LaTeX Project 接手维护;TeX Live 2024 中收录的是 v3.01(2023 年 11 月 15 日)。它的设计要旨是不依赖驱动——同一份颜色指定,无论走 pdfTeX 还是走 dvips,都会被同样地解释。
这份对「不依赖驱动」的执着,在选项的设计上留有清晰的痕迹。旧的 color 宏包只要选中了 dvips 系的驱动,就会悄悄打开 dvipsnames 的 68 个颜色——于是文档在作者机器上编译得好好的,别人一换成 pdfTeX 就撞上未定义颜色。xcolor 取消了这个行为:只把这些色名交给明确写出 dvipsnames 的人,作为交换,让它们在所有驱动下都能用。默认可用的是 19 个基本色;在此之上按需添加 dvipsnames(68 色)、svgnames(151 个名称)或 x11names(317 个名称)。若要给表格着色,table 选项会把 colortbl 一并带来。
\usepackage{xcolor} % 19 base colours, always available
\usepackage[dvipsnames]{xcolor} % + 68 cmyk names from the dvips driver
\usepackage[svgnames]{xcolor} % + 151 names from the SVG 1.1 spec
\usepackage[x11names]{xcolor} % + 317 names from the X11 rgb.txt
\usepackage[dvipsnames,svgnames]{xcolor} % sets can be combined
\usepackage[table]{xcolor} % also loads colortbl for coloured tables\textcolor 与 \color 的区别,以及 \colorbox 和 \fcolorbox
\textcolor{red}{文字} 只给作为参数传入的那段文字着色,而 \color{red} 是一个开关,一直有效到所在的 TeX 分组结束。两者并不是两套机制:\textcolor 不过是替你补上花括号的 \color——xcolor 的文档就是这么写的。所以一旦忘了 {\color{blue} …} 外面那对 { },后面整篇文档都会变蓝。反过来看,这也意味着在某个章节或环境内部使用 \color,到该环境结束时会自动复原。\textcolor 还会额外调用 \leavevmode,因此把它放在段首也不会破坏首行缩进。
This word is \textcolor{red}{red}, the rest is not.
{\color{blue} Everything from here to the closing brace is blue.} Back to black.
% the whole box gets a background; the frame colour comes first
\colorbox{yellow}{highlighted}
\fcolorbox{red}{yellow}{red frame, yellow fill}给背景上色的是 \colorbox{颜色}{文字},分别指定边框和填充的是 \fcolorbox{边框色}{背景色}{文字},参数顺序是「先边框,后填充」。这里有个很容易踩的坑:\colorbox 顾名思义就是一个盒子,其内容无法跨行断开。给长句加高亮,它不会换行,而是直接冲出版心,并留下一条 Overfull \hbox 警告。突出几个词是够用的,但若想按句着色,就该改用能处理断行的机制,比如 soul 的 \hl 或 tcolorbox。另外要注意,其他宏包的颜色参数——手册自己举的例子是 soul 的 \sethlcolor——未必能直接接受 red!30!white 这样的颜色表达式。这时先用 \colorlet 给混色起个名字,再把名字传进去。
| 命令 | 作用 | 有效范围 |
|---|---|---|
\textcolor{c}{text} | 把参数中的文字设为颜色 c | 仅限该参数 |
\color{c} | 从此处起把文字颜色切换为 c | 直到当前 TeX 分组结束 |
\colorbox{c}{text} | 把文字放在以 c 填充的盒子上 | 一个盒子;无法跨行 |
\fcolorbox{f}{b}{text} | 以 f 为边框、b 为背景框住文字 | 同上;线宽由 \fboxrule 决定 |
\pagecolor{c} | 把整页底色设为 c | 全局声明;当前页及其后所有页 |
\nopagecolor | 取消底色,恢复为透明 | 并非所有驱动都支持;缺失时会在日志中留言 |
\pagecolor 为何无视分组:颜色其实是 \special
\pagecolor{black} 会改变当前页以及之后所有页面的底色。而且与 \color 不同,这是一条不尊重 TeX 分组的全局声明:写成 {\pagecolor{black} …},右花括号也不会把它恢复回去。要撤销就得明确写出 \nopagecolor(恢复透明)或 \pagecolor{white}。想只把封面涂黑时,最先绊倒人的就是这个性质,所以惯常做法是在 \maketitle 之后紧接着写上撤销。如果你要的其实不是整页底色,而是在特定页面叠加边框或水印,那么 eso-pic 一类工具更合适。
\pagecolor{black}\color{white}
\maketitle
% \pagecolor is global: it must be cancelled by hand
\nopagecolor\color{black}这种「全局」并不是没规矩,而正是开头提到的 \special 的本性。颜色并没有存放在 TeX 的盒子里,它只是留在输出文件中的一条便条。所以一旦翻页,便条就送不到了——早期的 pdfTeX 就有一个著名后果:在换页前设定的颜色,到下一页就丢失了。当时的对策是一个叫 pdfcolmk 的宏包,它利用 LaTeX 的标记机制,在每页开头重新声明一次「本页从这个颜色开始」。pdfTeX 1.40(2007 年)内建了颜色栈之后,这套手法就不再需要了。如今 pdfcolmk 只是一个什么都不做的兼容空壳,xcolor 的 fixpdftex 选项也只剩下一句警告 Package option 'fixpdftex' is obsolete and ignored。在现代 pdfLaTeX 中,颜色可以跨越换页保留下来。
\definecolor 与 \colorlet 该怎么选
\definecolor{名称}{模型}{值} 从数字构造颜色,\colorlet{名称}{颜色} 从已有的颜色构造颜色。 如果品牌规范扔给你的是「#1B3A6B」,那就用前者;如果需求是「跟正文一样的蓝,再暗一点」,那就用后者。两者都只需在导言区写一次,之后这个名字就和 red、blue 一样是普通色名。\colorlet 的参数可以直接写下一节要讲的颜色表达式,因此给混色命名是它在实务中最有用的角色:\colorlet{accent}{blue!30!black}。把文档中的颜色统统改为经由名称引用,调整整体色调就只是改导言区的一行。
\definecolor{myblue}{rgb}{0.2,0.4,0.8} % from numbers
\definecolor{brand}{HTML}{1B3A6B} % from a web hex code
\colorlet{accent}{blue!30!black} % from an existing colour
\colorlet{ruleline}{brand!25} % a 25 % tint of the brand colourrgb、RGB、HTML、cmyk、gray:该选哪个颜色模型
\definecolor 的第二个参数是 颜色模型,也就是把颜色写成数字的方式。实务上的选法很简单:跟手上数字的形式对齐就对了。拿到的是 Web 颜色代码就用 HTML,设计工具给出 0–255 的数值就用 RGB,印刷厂给来四色网点参数就用 cmyk。xcolor 会在内部互相转换,所以同一个颜色换个模型来写,结果并不会变。
| 模型 | 值的含义与范围 | 示例 |
|---|---|---|
rgb | 红、绿、蓝,各为 0–1 的小数 | {0.2,0.4,0.8} |
RGB | 同样是红、绿、蓝,整数 0–255 | {51,102,204} |
HTML | 六位十六进制 RRGGBB;不要写开头的 # | {FF8800} |
cmyk | 青、品红、黄、黑,各 0–1;用于印刷 | {0,0.5,1,0} |
gray | 单个明度值:0 为黑,1 为白 | {0.5} |
Gray | 同为明度,但取整数;默认上限为 15 | {8} |
hsb | 色相、饱和度、明度,各 0–1;便于旋转色相 | {0.6,0.8,0.9} |
wave | 可见光波长(nm),363–814;适合物理插图 | {589} |
named | 伪模型:引用已有色名而非数值 | {Periwinkle} |
% four spellings of one orange
\definecolor{o1}{rgb}{1,0.5,0}
\definecolor{o2}{RGB}{255,128,0}
\definecolor{o3}{HTML}{FF8000}
\definecolor{o4}{cmyk}{0,0.5,1,0}
% the model can also be given inline, in brackets, at the point of use
\textcolor[HTML]{FF8800}{a one-off orange}
\color[gray]{0.5}模型也可以在使用处用方括号直接给出。若某个颜色只用一次、不值得起名,写 \textcolor[HTML]{FF8800}{…} 或 \color[gray]{0.5} 就够了。表中最特别的是 wave:给它一个以纳米为单位的可见光波长,xcolor 会算出该波长看起来是什么样——\definecolor{sodium}{wave}{589} 得到的正是钠 D 线的黄橙色。要给光学或光谱插图配色,没有比这更诚实的指定方式了。相对地,named 是一个不含数值的伪模型,用于引用由 dvipsnames 等选项载入的既有色名。日常使用中,直接写色名就足够了。
red!30!white 里的 ! 到底在做什么:用颜色表达式混色
! 是分隔混合比例的记号,颜色A!数值!颜色B 的意思是「A 占该百分比,其余为 B」。把 red!50!blue 实际展开,得到的是 rgb 的 0.5,0,0.5——正好各占一半的紫色。省略颜色 B 的 red!20,其余部分会被视为白色:展开为 1,0.8,0.8,即 20 % 红加 80 % 白的淡粉。这正是 xcolor 与 color 决定性分道扬镳之处:不必让色名无限膨胀,就能从手上已有的颜色里取出需要的浓淡。
red!50!blue— 红蓝各半;展开为rgb的0.5,0,0.5red!20— 20 % 红,其余为白:做淡色(tint)的常规写法blue!30!black— 30 % 蓝、70 % 黑:做暗色(shade)的常规写法-red— 开头的减号表示补色;-red正好是青色(rgb的0,1,1)green!40!yellow!60— 表达式可以串联,从左往右依次混合gray!15至gray!25— 铺在文字背后大致就是这个浓度;饱和色难以阅读
颜色表达式在「任何需要颜色的地方」都能用:\textcolor{blue!30!black}{…}、\colorbox{yellow!40}{…}、\definecolor 与 \colorlet 的参数,以及给表格着色的 \rowcolor{gray!15},用的都是同一套记法。实务上真正有效的习惯,是不要把混色散落在正文里,而是用 \colorlet 给每一个混色命名一次。若 accent 和 accent!15 两个名字就能覆盖标题、边框和表格斑马纹,那么改变全文色调只需改导言区的一行。反之,把 blue!30!black 散写在四十处,日后改主意就变成了查找替换的苦工。
\colorlet{accent}{blue!30!black}
\colorlet{accentbg}{accent!15}
% then, in the body
\textcolor{accent}{a heading}
\colorbox{accentbg}{a quiet highlight}
\fcolorbox{accent}{accentbg}{framed and filled}有哪些色名可用:从 19 个基本色到 752 个
即便一个选项都不加,下面这 19 个颜色 也始终可用。它们由 xcolor.sty 本身定义,因此在任何驱动、任何文档类下都是同样的名字。作为混色的原料,这 19 色通常也够用了;一旦习惯了 gray!20、blue!30!black 这样的写法,对更多色名的需求就淡了。
black,blue,brown,cyan,darkgray,gray,green,lightgray,lime,magenta,olive,orange,pink,purple,red,teal,violet,white,yellow
若还不够,就用选项补充色名。dvipsnames 提供 dvips 驱动带来的 68 个颜色(RoyalBlue、Periwinkle、BurntOrange 等,全部以 cmyk 定义);svgnames 提供 151 个名称,即 SVG 1.1 规范的名字再加上 4 个取自 X11 的词;x11names 则提供取自 X11 rgb.txt 的 317 个名称。这里有一条 xcolor 手册自己写下的有趣注记——svgnames 的 151 个名字,实际只对应 141 种颜色,因为像 Gray 与 Grey 这样拼写不同的别名指向同一个值。x11names 同样是 317 个名字对应 315 种颜色。手册还提示:两个集合一起载入,rgb.txt 中全部 752 个名称就都触手可及了。
这些色名 区分大小写。把 RoyalBlue 写成 royalblue,编译就会停在 ! Package xcolor Error: Undefined color 'royalblue'.。忘记载入色名集合时也会看到同一条错误——比如只写了 \usepackage{xcolor} 却用了 Periwinkle。与其凭记忆猜每个名字长什么样,不如用 texdoc xcolor 打开手册:那里每个名字旁边都印着色样。还有一条边界值得记住:给表格行和单元格着色的 \rowcolor、\cellcolor、\rowcolors 归 colortbl 管,而不是 xcolor;\usepackage[table]{xcolor} 一行就能把两者都带上——详见彩色表格那一页。
屏幕上的颜色印不出来:印刷与无障碍的陷阱
要送去商业印刷的文档,用 rgb 写下的颜色未必就是印出来的样子。胶印只能靠 cmyk 四色油墨调色,而这个色域比屏幕的 RGB 更窄。给 xcolor 传入 cmyk 选项,文档中所有颜色都会被转换,例如 blue 会变成 {cmyk}{1,1,0,0}。屏幕上鲜亮的蓝,到了纸上可能是一片发闷的紫藏青——这种落差并不罕见。送印之前,要么先用 \usepackage[cmyk]{xcolor} 排一遍并核对打样,要么从一开始就用 cmyk 定义颜色,这样更稳妥。
再深一层的陷阱是:把颜色当作唯一的线索。红色行代表警告、绿色行代表正常——这样的设计,在页面被黑白复印的那一刻、在有色觉障碍的读者眼前、或者在电子墨水屏上,会把信息整个丢掉。网页无障碍标准 WCAG 的「颜色的使用」(成功标准 1.4.1)要求的正是这一点:颜色必须与另一种线索并用——符号、加粗、明确的标签。所幸,检验手段就在 xcolor 自己手里。写下 \usepackage[gray]{xcolor},所有颜色都会转换到 gray 模型(red 变成 {gray}{0.3}),直接模拟黑白打印机的输出。如果在这个状态下含义依然传达得出来,这份文档就没有过度依赖颜色。
% convert every colour in the document to cmyk, for offset printing
\usepackage[cmyk]{xcolor}
% convert every colour to gray: a one-word black-and-white proof
\usepackage[gray]{xcolor}