2018 年以后的 LaTeX,café 的 é、señor 的 ñ、Gauß 的 ß,都可以直接敲进源文件就排出来。可是打开这样生成的 PDF,搜索「café」,却可能一条都搜不到。原因不在重音符号本身,而在导言区的一行——fontenc 的设置。本页把带重音的西文当作一条完整的链路来追踪:从输入到 PDF 的内容,包括输入编码、用 babel 声明语言、.bib 里的作者姓名,以及 hyperref 的书签。事故总是发生在两端。重音命令本身的清单留给相关页面。
\usepackage[utf8]{inputenc} 已经不必再写
自 2018 年起,UTF-8 就是 LaTeX 的默认输入编码。 一份完全不加载 inputenc、直接写 Un café à Montréal, señor Gauß, Kovář, Łódź. 的文档,在 TeX Live 2024 的 pdfLaTeX 下编译不会给出任何警告。LaTeX News 28(2018 年)记录了把默认从「原样透传」改为 UTF-8 这件事,并说明 \usepackage[utf8]{inputenc} 因此不再必需,写上也无害。所以搜索 inputenc utf8 找到的旧文,大多是在讲一个已经不存在的问题。
XeLaTeX 与 LuaLaTeX 本来就以 UTF-8 为唯一输入编码,inputenc 在那里从一开始就无事可做。但这并不会报错。 用 TeX Live 2024 附带的 inputenc.sty(2021/02/14 v1.3d)把 inputenc 交给这两个引擎,得到的是一条警告——Package inputenc Warning: inputenc package ignored with utf8 based engines.——编译照常跑完。旧导言区搬到 LuaLaTeX 上不会中断正是因为如此;反过来说,「没报错」并不等于这行写对了。下次整理导言区时顺手删掉即可。
顺带一提。UTF-8 这套编码方案本身,是 1992 年由 肯·汤普森 与 罗布·派克 设计的,LaTeX News 28 也提到了这段来历。TeX 诞生的 1970 年代末只有 7 位 ASCII,要打出 é 只能写命令。今天能直接敲 é,是这段四十年弯路走到了尽头。
用 pdfLaTeX 就一定要加 \usepackage[T1]{fontenc}
inputenc 决定「怎样读源文件的字节」,而 fontenc 决定「字体的第几个位置放什么字形」。默认的 OT1 只有 128 个位置,没有把带重音的字母作为单个字形收进去。所以 é 是作为合成输出的:在 e 上面画一个锐音符。看上去几乎一样,但在 TeX 看来那不是「一个字」,而是「字与符号的叠加」,于是含它的词完全退出了连字符断词(哪个命令在哪种编码里可用,已整理在相关页面的表中)。T1 有 256 个位置,把 é、ř、ł 都作为单个字形收了进去。
如果你看到「用 T1 会把 Computer Modern 变成位图、把 PDF 弄丑」这类建议,那是过时的信息。在 TeX Live 2024 下,只加 \usepackage[T1]{fontenc} 生成的 PDF 用 pdffonts 一查,嵌入的是来自 cm-super 的 SFRM1000,类型是 Type 1——不是位图(Type 3)。不过再加上 lmodern 仍然值得:嵌入的字体会变成 LMRoman10-Regular,而 Latin Modern 是把 Computer Modern 的设计为 Unicode 时代重绘的产物,字符覆盖与字体文件的来路都更干净。
在 XeLaTeX 和 LuaLaTeX 下,fontenc 没有用武之地。 它们全程以 Unicode 处理系统字体,只要用 fontspec 选好字体,带重音的字母自然出现。所以判断很简单,是个二选一:pdfLaTeX 用 T1(最好再加 lmodern),Xe/LuaLaTeX 用 fontspec。 另外,近年的 LaTeX 内核带有相当广的 Unicode 映射表,即便是 pdfLaTeX 加 T1,直接敲 €、→、–、“ ” 也能无警告排出。全角符号则另当别论,那部分归「西文的排法」页。
% pdfLaTeX: the two lines that matter
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern} % optional, but cleaner glyph coverage
% (no inputenc: UTF-8 has been the default since 2018)
\begin{document}
Un café à Montréal, señor Gauß, Kovář, Łódź.
\end{document}
% XeLaTeX / LuaLaTeX: no fontenc at all
% \usepackage{fontspec}
% \setmainfont{Latin Modern Roman}PDF 里搜不到「café」——那个字被拆成了两个字符
把同一份源文件 Un café à Montréal, señor Gauß, Kovář, Łódź. 编译两次——一次不加 fontenc(默认 OT1),一次加 [T1]{fontenc}——再用 pdftotext 抽出文字并查看码位,差别是决定性的。OT1 版里的 é 被存成两个字符:「e」加上 U+0301(组合用锐音符)。 所以在 PDF 阅读器里输入「café」(é 为 U+00E9)根本对不上。实测搜索 café、Montréal、Łódź,三个词全部 0 命中。T1 版在同样位置放的是 预组合的单个字符——U+00E9、U+00E1、U+0159、U+0141、U+017A——三个词都能找到。
还有更直白的坏法。波兰语的 Ł(带斜线的 L)在 OT1 里没有字形,于是 LaTeX 改用在 L 上叠一道斜线来画。肉眼看是 Ł,但从 PDF 抽出的字符串不是 Łódź,而是 Lódź——斜线没了,只剩一个普通的 L。作者名或地名以这种状态出版,正文看着没问题,检索和复制进文献管理软件时拼写却对不上,留下难缠的偏差。出于同样的原因,OT1 下带重音的词也无法连字符断行(那项实测在相关的「重音符号」页)。
# extract the text layer and inspect the code points
pdftotext paper.pdf - | head -1
# default OT1 -> e is followed by U+0301, a separate combining acute,
# and the bar of L is lost entirely:
# searching for "café" / "Montréal" / "Łódź" gives 0 hits
# with T1 -> precomposed U+00E9 U+00E1 U+0159 U+0141 U+017A:
# all three words are found
# and check what font actually got embedded
pdffonts paper.pdf用 babel 或 polyglossia 声明语言,连输入方式都会变
把语言交给 babel,该语言的输入简写就会生效。在加了 \usepackage[ngerman]{babel} 的文档里,"a、"o、"u、"s 分别变成 ä、ö、ü、ß,而双引号简写的两种形式会给出德语的下引号 „ 与上引号 “。"- 更进一步:它添加一个允许断词的位置,所以写 Zucker"-dose 排出来仍是 Zuckerdose——记号本身不会出现在纸上。在没有德语键盘的环境里写德语,或者想让原稿保持纯 ASCII 时,这一招很管用。
在 XeLaTeX 和 LuaLaTeX 下,同样的角色由 polyglossia 承担,写法如 \setmainlanguage{german}。无论用哪一个,都要留意简写会改写 " 的含义:在 verbatim 之外的 URL、代码片段、文件名里直接敲 ",可能排出意料之外的字符。在引文或外部文件名密集的小节里,用 \shorthandoff{"} 临时关闭最为稳妥。另外,babel 不只改变输入,还改变连字符规则与各语言的排版惯例,那部分归「西文的排法」页。
\usepackage[T1]{fontenc}
\usepackage[ngerman]{babel}
% "a "o "u "s -> a-umlaut, o-umlaut, u-umlaut, eszett
% "- -> an extra hyphenation point, invisible in the output
% Zucker"-dose still prints as one word
\shorthandoff{"} % turn the shorthands off around URLs and code.bib 里的作者姓名——BibTeX 会把 É 排到 Z 后面
重音带来的事故,最显眼的不是正文而是文献表。不妨一试:往 .bib 里放 Alpha、Ore、Zola、Zulu 四条,其中 Émile Zola 与 Øystein Ore 用原样的 UTF-8 书写。用 plain.bst 跑 BibTeX,出来的顺序是 Alpha、Zulu、Zola、Ore——以 É 和 Ø 开头的两条掉到了 Z 后面。BibTeX 把姓名当作字节串比较,而 UTF-8 的 É 首字节恰好大于 z,如此而已。既无警告也无错误。 只把这两处作者字段改写成命令记法 {\'E}mile、{\O}ystein,顺序就变成正确的 Alpha、Ore、Zola、Zulu。
这个毛病是有来由的。TeX Live 2024 里的 BibTeX 启动时仍自报 Version 0.99d——这是 Oren Patashnik 在 1980 年代写的程序,服役近四十年却始终没有到达 1.0。它原封不动地保留着 7 位时代的设计,自然没有 Unicode 的排序规则。相比之下,biblatex 配 biber 懂 Unicode,能在一个字都不改的前提下把同一份 UTF-8 的 .bib 排对(已用 biber 2.19 验证)。所以实务上的判断分两路:若投稿方允许用 biber,就保持 UTF-8;若被要求用 BibTeX,就只把 .bib 统一成命令记法。 无论选哪条,关键都是别混用。混用的 .bib 里,去重和排序都靠不住。
% BibTeX 0.99d + plain.bst sorts these as Alpha, Zulu, Zola, Ore
@article{a1, author = {Émile Zola}, title = {Un titre}, journal = {J}, year = {2001}}
@article{a3, author = {Øystein Ore}, title = {Another}, journal = {J}, year = {2003}}
% ...and these as Alpha, Ore, Zola, Zulu -- correct
@article{a1, author = {{\'E}mile Zola}, title = {Un titre}, journal = {J}, year = {2001}}
@article{a3, author = {{\O}ystein Ore}, title = {Another}, journal = {J}, year = {2003}}
% biblatex + biber sorts the UTF-8 form correctly with no rewriting:
% \usepackage[backend=biber]{biblatex}书签与 PDF 字符串——hyperref 会把重音传过去
PDF 的书签与文档信息,是作为「PDF 字符串」另行写入的,与正文分开。以前重音在这里丢失是家常便饭;如今默认就是 Unicode。用 TeX Live 2024 的 hyperref 7.01h 排 \section{Le café de Montréal},生成的 .out 文件里在 UTF-16 字节序标记之后原样收着 é(U+00E9),书签栏也显示正确。已经不需要手动加 unicode 选项了。
不过 PDF 字符串并不接受正文里的一切命令。在标题里放公式,日志就会排满 Package hyperref Warning: Token not allowed in a PDF string (Unicode): removing 'math shift',相关记号会被悄悄从书签中剔除。正文正确、书签却看不懂的局面正是这样产生的。解法是 \texorpdfstring{}{}:第一个参数供排版,第二个参数是给书签用的纯文本。带重音的词可以直接写进第二个参数——既然 PDF 字符串是 Unicode,café 就以 café 的样子通过。
\usepackage{hyperref}
% the log fills with "Token not allowed in a PDF string"
\section{Le café de Montréal $x^2$}
% typeset form on the left, bookmark text on the right
\section{Le café de Montréal \texorpdfstring{$x^2$}{x2}}合作写作前先定下来的事
重音的输入方针,属于稿子一大就难以回头的那类决定。同一个人名既写成 Gödel 又写成 G\"{o}del 的稿子,两种都能正确排出,但检索、批量替换、去重每一样都要做两遍。而且发现不一致的时刻,往往是交稿前扫一眼文献表的时候。在第一次提交时就把三件事定下来——引擎、fontenc 那一行、.bib 的记法——是最省事的路。
- 正文统一用 UTF-8 直接输入。 不要写
inputenc。一行即可:Xe/LuaLaTeX 用fontspec,pdfLaTeX 用\usepackage[T1]{fontenc}。 - 做一次验收测试。 在生成的 PDF 里搜索
café和某个作者名,确认能搜到。搜不到就说明少了fontenc那行。 .bib要与投稿方的工具链匹配。 允许 biber 就保持 UTF-8;被要求用 BibTeX,就把所有作者名统一成{\'E}形式——两者绝不混用。- 标题里只要有公式,就配上
\texorpdfstring。Token not allowed in a PDF string警告就是书签被弄坏的信号。 - 若使用
babel简写,就在 URL 和代码周围加\shorthandoff{"}。 人们很容易忘记引号的含义已被改写。