字体样式命令

在 LaTeX 里写 {\bf\it x},得到的不是粗斜体,而是斜体。把顺序换成 {\it\bf x},消失的反倒是斜体,只剩直立的粗体。这些老式的双字母命令 就是不肯相加,原因是 article.cls 第 493 行里的一个词。读懂那一行,也就一并明白了现代 LaTeX 为什么给每个字体开关都准备了两种形式。本页梳理这些命令如何排布在 字族、字重、字形三个独立的轴 上,何时该用带参数的 \textbf{...}、何时该用按范围生效的声明 \bfseries,以及当你要粗体小型大写字母却拿到普通粗体时,日志到底在说什么——全程附实测数据。

\textbf{...}\bfseries 的区别

同一种切换备有两种写法。以 \text… 开头的命令形式带一个参数,只作用于花括号内部。以 …family / …series / …shape 结尾的声明形式不带参数,从该处一直生效到所在分组或环境结束。 选哪种取决于范围长短:几个词用 \textbf{只有这里},整段或整个环境用 {\bfseries …}\begin{quote}\itshape …\end{quote}。若忘了用分组括住声明,它会一路生效到文档末尾,所以务必核对 {} 的配对。宏包和文档类的内部代码也偏爱声明形式,因为它不额外添加任何东西,把范围管理留给调用方。

latex
% command form: for a short span
This is \textbf{bold} and this is \textit{italic}.

% declaration form: a group limits the scope
Normal here. {\bfseries Bold from here on.} Back to normal.

% declaration applied to a whole environment
\begin{quote}
  \itshape The whole quotation is set in italic.
\end{quote}

除了范围之外,两种形式还有一个实际差别:命令形式会自动加入斜体校正,声明形式不会。 倾斜字形的外廓向右上探出,而排版宽度并不含这一段,于是紧随其后的直立字母就会相撞。实测 \textit{f}h 为 10.74167pt,{\itshape f}h 为 8.62222pt,这 2.11945pt 的差额整个就是缺失的空白。若用声明形式,就得自己在倾斜段落末尾写上 \/——{\itshape f\/}h 实测 10.74167pt,与命令形式完全一致。这项校正究竟在做什么,强调那一页有详细说明。

字族、字重、字形 — 决定字体的三个轴

LaTeX2e 的字体由三个彼此独立的属性组合而成:字族(family)是字的骨架,字重(series)兼含粗细与宽度,字形(shape)是倾斜或形态。 切换其中一个轴,另外两个保持不变——这种正交性正是整套设计的核心,也正因如此,「保持无衬线而加粗」「保持粗体而变斜」才能顺畅写出。这套方案以 NFSS(New Font Selection Scheme) 之名于 1989 年问世,1993 年推出第二版,1994 年随 LaTeX2e 成为标准;更早的 LaTeX 2.09 根本没有字体属性这个概念。值得记住的是,字重是一个同时涵盖粗细与宽度的轴。这也正是 Computer Modern 的粗体写作 bx(bold extended,既粗又宽)的原因。

  • 字族(family) — 罗马体(有衬线)\textrm / \rmfamily、无衬线体 \textsf / \sffamily、打字机体(等宽)\texttt / \ttfamily
  • 字重(series) — 中等(默认粗细)\textmd / \mdseries、粗体 \textbf / \bfseries。属性值是最多两个字母的字符串,把粗细与宽度并写在一起:m(medium)、b(bold)、bx(bold extended)、c(condensed)等。
  • 字形(shape) — 直立(默认)\textup / \upshape、斜体 \textit / \itshape、倾斜 \textsl / \slshape、小型大写字母 \textsc / \scshape。属性值依次为 nitslsc
  • 斜体与倾斜不是一回事。 \itshape 调用的是单独绘制的字体(Computer Modern 中为 cmti10),\slshape 调用的是把直立体机械倾斜后的字体(cmsl10)。字形本身就不同——比如斜体的 a 是单层结构。
轴与效果命令形式(短范围)声明形式(用分组限定)
roman字族:罗马体(有衬线)\textrm{...}{\rmfamily ...}
sans字族:无衬线体\textsf{...}{\sffamily ...}
typewriter字族:打字机体(等宽)\texttt{...}{\ttfamily ...}
bold字重:粗体(属性值如 bx\textbf{...}{\bfseries ...}
medium字重:中等(默认,属性值 m\textmd{...}{\mdseries ...}
italic字形:斜体(属性值 it\textit{...}{\itshape ...}
slanted字形:倾斜(属性值 sl\textsl{...}{\slshape ...}
small caps字形:小型大写字母(属性值 sc\textsc{...}{\scshape ...}
upright字形:回到直立(属性值 n\textup{...}{\upshape ...}
reset all把三个轴全部重置为文档默认值\textnormal{...}{\normalfont ...}

同时套用粗体与斜体

\textbf{\textit{...}} 就能得到粗斜体。顺序无所谓,用声明形式时只需并排列出:{\bfseries\itshape ...} 由于三个轴彼此独立,后写的开关绝不会抹掉先写的。实测确认:在 \textbf{\textit{Y}} 内部,字重保持 bx,只有字形变成 it,于是载入了 Computer Modern 的粗斜体 cmbxti10。三个轴一起用,\textsf{\textbf{\textit{...}}} 就是无衬线粗斜体。不过,「能写出」某个组合与「字体拥有」它是两码事:当所要的字面并不存在时,LaTeX 会替换成相近的——这正是下一节的主题。

latex
% nest the command forms
\textsf{\textbf{\textit{Bold sans-serif italic}}}

% or list the declarations in one group (equivalent)
{\sffamily\bfseries\itshape Bold sans-serif italic\/}

% one word back to the document default inside a sans-serif run
{\sffamily A heading-like line with one \textnormal{normal} word}

要把三个轴一次性重置为文档默认值,就用 \textnormal{...}(命令形式)或 \normalfont(声明形式)——当无衬线标题中需要一个词用正文字体时非常方便。只想重置某一个轴,就调用指向该轴默认值的命令:\rmfamily\mdseries\upshape。顺带一提值得知道的是:latex.ltx 第 11158 至 11170 行是一连串 \DeclareTextFontCommand 调用,所以这里列出的每一个 \text… 都是由对应的声明机械地造出来的。同一段还定义了 \textulc(回到大小写混排)与 \textsw(花体),只是真正拥有这些字形的字体寥寥无几。

为什么 \bf\it 不肯组合

因为 \bf 的定义里含有 \normalfont article.cls 第 490 至 496 行这样罗列这些老式双字母命令——\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf}\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit}\rm\sf\tt\sl\sc 也是同一格式。也就是说,\bf 不是「变粗体」的命令,而是 「先把三个轴全部重置为默认值,再变粗体」 的命令。排 {\bf\it x} 时,藏在 \it 里的 \normalfont 把先前的 \bfseries 整个丢掉:实测字重从 bx 退回 m,只剩字形 it。反过来 {\it\bf x},字形回到 n,只剩字重 bx。新旧混用也一样:\textbf{\tt x} 最终字族是 cmtt,而字重退回 m\textbf 被无声地丢弃。

latex
% the old two-letter commands do not stack
{\bf\it x}          % italic only  -- the bold is thrown away
{\it\bf x}          % upright bold -- the italic is thrown away
\textbf{\tt x}      % typewriter, medium weight -- \textbf silently lost

% the modern commands do stack
{\bfseries\itshape x}
\textbf{\ttfamily x}

这种非正交性并非缺陷,而是原样保留了 1994 年之前 LaTeX 2.09 的字体模型:当时「当前字体」是一个单独的名字而非一组属性,因此每次切换都是整体替换。LaTeX2e 于 1994 年引入了可按轴叠加的现代命令,并通过 \DeclareOldFontCommand 把老命令保留下来,作为 让既有文档仍能编译的兼容层。它们至今仍能无错运行——而这恰恰是麻烦所在:写下 {\bf\it 重要} 的人本意是粗斜体,不看 PDF 就不会发现不对。另外,\DeclareOldFontCommand 的第三个参数是数学模式下的含义,所以 \bf 在公式里表现为 \mathbf。一个命令在正文与公式中含义不同,这是又一条避开它们的理由。新写的代码请一律使用 \textbf / \bfseries 一族。

当日志出现 Font shape ... undefined

这不是拼写错误的报告,而是在说:字体里没有你所要的那个组合的字面。 典型例子是粗体小型大写字母——默认 OT1 编码下的 Computer Modern 没有同时兼具 bxsc 的字面。排 \textsc{\textbf{...}} 时,日志里会成对出现下面这样的 Font shape 行与 using ... instead 行。LaTeX 不会报错停止,而是丢掉小型大写字母、继续用普通粗体排下去。于是你只有在看 PDF 时才会发现「指定的小型大写字母没出来」。同理,\textsw{...} 也会警告 OT1/cmr/m/sw 未定义。

log
LaTeX Font Warning: Font shape `OT1/cmr/bx/sc' undefined
(Font)              using `OT1/cmr/bx/n' instead on input line 8.

LaTeX Font Warning: Some font shapes were not available, defaults substituted.

对这条特定的警告,有一行就能解决的办法:加上 \usepackage[T1]{fontenc} T1 编码的 Computer Modern(EC 字体)确实带有粗体小型大写字母,因此同样的 \textsc{\textbf{...}} 可以无警告通过;实测加载 [T1]{fontenc} 重新编译后,日志里的字体警告完全消失了。T1 还改善了西文重音字母与断词的处理,所以这类文档迟早都会加上这一行。当警告指向别的组合时,先读日志里那个四段式名字——OT1/cmr/bx/sc,即编码/字族/字重/字形——判断缺的是哪个轴。然后做决定:换成拥有它的字体、把要求降一级,或者接受替换——并把这个决定记录下来。

latex
% OT1 Computer Modern has no bold small caps -> warning + silent fallback
% T1 (the EC fonts) does have it
\usepackage[T1]{fontenc}

\textsc{\textbf{Bold Small Capitals}}

% low-level NFSS: name the four attributes, then commit with \selectfont
{\fontfamily{cmr}\fontseries{bx}\fontshape{sc}\selectfont Explicit}

正如最后两行所示,\textbf 这类高层命令在内部会展开为 NFSS 的低层命令\fontfamily{...}\fontseries{...}\fontshape{...} 改写属性,而 \selectfont 才依据此刻这四个属性真正选定字体。仅仅设置属性什么也不会发生;不调用 \selectfont,字体就不会改变。平时无需直接触碰这一层,但当你想按名字调出某个特定字面,或想确认警告里那个四段式名字究竟指什么时,它就派上用场了。编码的含义以及如何加载真实的字体文件,留给字体系统那一页。

日文的字体切换 — 明朝体与哥特体

在 pLaTeX / upLaTeX 中,日文拥有独立于西文的轴,字族一轴有两个成员:明朝体 \textmc / \mcfamily 与哥特体 \textgt / \gtfamily 定义始于 plcore.ltx 第 1471 行,切换的是日文专属属性 \kanjifamily。有趣的是 \bfseries 作用于日文时的行为。实测显示,日文字族保持 mc(明朝),只有字重变为 bx。而 jy1mc.fd 第 25 行写着 \DeclareFontShape{JY1}{mc}{bx}{n}{<->ssub*gt/m/n}{}:请求「明朝的粗体」时,改由标准字重的哥特体来提供。也就是说,「一加粗就变哥特体」既非偶然也非退让,而是字体定义文件中明文写就的正式替换,如实反映了日文强调历来靠「切换字体」而非加粗的传统。

稿件中的运用规则

直接写进正文的字体命令,请只保留那些承载含义的。 在学位论文或技术文档里,把字体开关当作「稍微调一下外观的小工具」四处播撒,日后就越难修改。「标题加粗」「图注变小」「定义术语一律无衬线」这类规则,应该放进文档类、宏包或你自己的语义宏里,而不是每次都现敲一个 \textbf。中间加一层——\newcommand{\term}[1]{\textsf{#1}}——方针变更时就只需改一行。反过来,对那些「外观本身就是含义」的东西,比如界面上的按钮名或键盘上的按键名,在正文里直接用 \textsf\texttt 完全没问题。判断标准很简单:外观是目的,还是含义的替身。

  • 把强调交给 \emph 一旦把外观选择嵌进正文,日后就再也改不动强调的样子了。
  • 长范围用声明形式。 整段引文或整条旁注,就在环境开头声明 \itshape,让环境自己关闭这个分组。
  • 组合先在最小示例里试。 先在一个简短文档里排一次 \textsf{\textbf{...}}\textsc{...},确认日志没有字体警告,再撒进正文。
  • 别放任替换不管。 忽视警告,同样的标记可能逐章看起来不同。换字体,或者把要求简化。
  • 不要混入旧的双字母命令。 在合作稿件里看到 \bf\it,就换成 \bfseries / \itshape\textbf / \textit。它们看似能用,实则不会组合。