零、
我就不写成《LaTeX 笔记(一):NFSS 那点事儿》了,省得你们指望还有二……
本文比较靠近 TeXnique 这个主题。想理解本文的内容,至少也得读一下 TeXbook 或者 TeX by topic 之类的。
一、
NFSS 框架是 LaTeX2e 的核心内容,占了 LaTeX2e 源码的 30% 左右的篇幅。如果你不理解它的重要性,还有一个数据:在完全安装的 TeX Live 发行版中,各类字体宏包编写的字体定义文件 (*.fd) 有 3000+。有了 NFSS,大家才能快乐地使用 \bfseries \itshape 等命令切换字体,随心所欲不逾矩了。
那么没有 NFSS 的情况下怎么切换字体呢?如果你像第 0 条里面说的,读过了两本书之一,那么你就知道是这么来的:
% 以 txfonts 宏包的字体 txr 为例
\font\txrm=txr at 12pt
\txrm Hello, \TeX\ and its fonts!
需要定义一个控制序列(命令)为字体,然后在切换字体的时候用一下。plain TeX 已经定义了一些,如 \bf \it \tt 之类。问题在于,\bf 是直立的粗体,\it 是普通字重的意大利斜体,如果你想要粗斜体,这个字体跟它俩一毛钱关系都没有,必须你自己重新定义个诸如 \bfit 之类的字体命令才行。就连不同字号的字体,都需要重新定义。
那么话说回来,其实 \bfseries \itshape 和 \mdseries \itshape 、\bfseries \upshape 等命令给出的字体也全都不一样啊,这怎么搞的?往下看。
<!-- 往下看之前建议仔细看看 TeX 发行版里两篇重要的文档 encguide 和 fntguide。-->
<!-- 什么,你看完了?-->
<!-- 我也不知道你真看完了还是假看完了,好吧,继续 -->
二、
上面让看的两篇文档,介绍了 NFSS 重要的属性——坐标。一个字体有 encoding / family / series / shape 和字号这五个坐标,比如默认字体,10pt 的 Computer Modern Roman (cmr10),在 NFSS 的坐标是 OT1/cmr/m/n/10。这里就不铺开讲了,需要你真的看完那两篇文档。
哦,你似乎在哪儿见到过这么一串写法?对了,在 Overfull \hbox 等信息里满都是这种写法。好了,是时候告诉你真相了:在调用这个字体前,实际上就有这么个定义:
\global\expandafter\font\csname OT1/cmr/m/n/10\endcsname=cmr10
也就是说,真实的字体命令,是由这些坐标拼起来构建的。不同的坐标会构造出来不同的字体,你每切换一次坐标,很可能就要生成一个新的字体命令。
三、
真的就是把字体坐标拼起来这么简单?当然不止,LaTeX 需要额外的信息找到上面代码里等号后面要赋值的字体名。把坐标与字体名称建立映射的东西,就是每个字体宏包必不可少的 .fd 文件。这个放到后面穿插着说。
四、
现在要提到 NFSS 里的一个比较核心的命令——\selectfont。在 \bfseries \itshape 或者 \small \large 等的定义里都有它。你光用 \fontseries{bx} 或者 \fontsize{10}{12} 改坐标还起不到改字体的作用,它只是把坐标信息存到了一些内部宏,如 \f@encoding \f@series 等。\selectfont 才是改变字体的实际操作。
那么 \selectfont 主要干了些啥呢?
\DeclareRobustCommand\selectfont
{%
\ifx\f@linespread\baselinestretch \else
\set@fontsize\baselinestretch\f@size\f@baselineskip \fi
\xdef\font@name{%
\csname\curr@fontshape/\f@size\endcsname}%
\pickup@font
\font@name
\size@update
\enc@update
}
定义里,前两行先不细看,它的作用是处理修改行距的两个命令 \linespread{***} 和 \renewcommand {\baselinestretch}{***} 可能导致的冲突;
第3-4行就是定义出来所谓的 \OT1/cmr/m/n/10 了,这里定义了一个全局的 \font@name,后面很多地方用得着,另外一个重要的内部宏是 \curr@fontshape,它储存着前四个坐标。
第6行则是使用这个 \OT1/cmr/m/n/10 改变字体了;第7行起到的作用是更新 \baselineskip 等一系列工作;第8行是改了 encoding 之后的一些处理。
重点看第5行:
\def\pickup@font{%
\expandafter \ifx \font@name \relax
\define@newfont
\fi}
意思不难,如果 \OT1/cmr/m/n/10 已经定义过,拿来现成用就好,否则需要定义新字体了。接下来看:
\def\define@newfont{%
\begingroup
\let\typeout\@font@info
\escapechar\m@ne
\expandafter\expandafter\expandafter
\split@name\expandafter\string\font@name\@nil
\try@load@fontshape % try always
\expandafter\ifx
\csname\curr@fontshape\endcsname \relax
\wrong@fontshape\else
\extract@font\fi
\endgroup}
group 之内,第一行影响到向终端输出的信息;第2-4行是对 \font@name 内容的一个拆包,把它里头的字体坐标存进 \f@encoding \f@seris 等。为什么不直接用那几个缓存的结果呢,不得而知,可能有考虑 \font@name 本身被动过手脚,跟缓存的结果不一致的情况。
后面的一个关键宏是 \try@load@fontshape:
\def\try@load@fontshape{%
\expandafter
\ifx\csname \f@encoding+\f@family\endcsname\relax
\@font@info{Try loading font information for
\f@encoding+\f@family}%
\global\expandafter\let
\csname\f@encoding+\f@family\endcsname\@empty
\nfss@catcodes
\let\nfss@catcodes\relax
\edef\reserved@a{%
\lowercase{%
\noexpand\InputIfFileExists{\f@encoding\f@family.fd}}}%
\reserved@a\relax
{\@input@{\f@encoding\f@family.fd}}%
\fi}
我们可以看到,这个宏起到的作用就是载入 .fd 字体定义文件了。在此解释两个宏:
- \csname \f@encoding+\f@family\endcsname,形如 \OT1+cmr。它的定义标志着 .fd 文件被载入。它是 .fd 文件里 \DeclareFontEncoding 命令定义出来的。
- \csname \curr@fontshape\endcsname,形如 \OT1/cmr/m/n,它的定义标志着 .fd 文件被载入,并且前四个坐标被 .fd 文件里的 \DeclareFontFamily 命令定义过,这样就可以拿来把最后一块拼图,也就是字号信息拼上。
发表评论 取消回复