零、

我就不写成《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\font\OT1/cmr/m/n/10=cmr10

不过呢直接这样写是错的,因为控制序列的名称必须是字母类(catcode=11 的字符)。正确的写法相当于这样:

\global\expandafter\font\csname OT1/cmr/m/n/10\endcsname=cmr10

\expandafter 和 \csname ...\endcsname 的用法如果不明白,赶紧去看书。

也就是说,真实的字体命令,是由这些坐标拼起来构建的。不同的坐标会构造出来不同的字体命令,你每切换一次坐标,很可能就要生成一个新的字体命令。

三、

真的就是把字体坐标拼起来这么简单?当然不止,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}{***},后者会导致 \baselinestretch 和用内部命令缓存的行距扩展系数 \f@linespread 不一致(前者则没有这个问题),所以在这儿要处理一下。

第 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@series 等。

后面的一个关键宏是 \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 命令定义过,这样就可以拿来把最后一块拼图,也就是字号信息拼上。

那么好了,往下走,如果 .fd 文件载入了,各条 \DeclareFontFamily 也过了一遍,仍然找不到对应的字体怎么办?这就表明,字体包里可能没有定义你要的坐标,这时就要进行回退(fallback),这个工作由 \wrong@fontshape 完成。每个 encoding 的定义文件如 t1enc.def 里,都会用 \DeclareFontSubstitution 命令给出用来回退的三个坐标(OT1 的是在 LaTeX2e 内核里给的)。

\wrong@fontshape 的代码就不继续贴了,举个例子,如果我弄了一个错误的坐标出来, \OT1/cmr/xxx/xxx,LaTeX 载入了 ot1cmr.fd 发现没有这个坐标,它就回退到 \OT1/cmr/xxx/n,还找不到,就回退到 \OT1/cmr/m/n。如果进行了回退,你会看到 "Font shape *** undefined, use *** instead" 的提示。

如果找到了前四个坐标的信息,或者回退到了合适的前四个坐标,接下来就要通过字号这个坐标去找字体(TFM)了。这个功能由 \extract@font 完成,它调用了 \get@external@font 去解析 \DeclareFontShape 命令里各种复杂的字号和 tfm 映射关系的定义式,后者内部用了 \try@size@range 完成实际的解析,用 \try@size@substitution 完成字号的 fallback。

五、

啰啰嗦嗦地写完了,不知道有多少人有足够的耐心看到这儿,以及为了看懂它去看更多的书。LaTeX 对字体的这套解决方案也许不是最优的,不过恐怕是无法替代的,不然那乌泱乌泱的字体宏包都得改。

嗯我不是说了有 30% 的代码是 NFSS 嘛?事实上今天讨论到的这部分连 5% 都占不到。其余的大头是被 encoding 和数学字体占了。encoding 的部分,要解决的是不同字体编码下的一些命令和重音的实现,比如 OT1 下的 \"o 是两个字符拼起来的,而 T1 则可以找到单个的字符。数学字体就不在这里说了,要说起来肯定要另开一帖。

\endinput

作者:Louis Stuart
链接:https://zhuanlan.zhihu.com/p/21339128
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

LaTeX 笔记:NFSS 那点事儿相关推荐

  1. $\LaTeX$笔记:Section 编号方式(数字、字母、罗马)计数器计数形式修改

    $\LaTeX$系列根目录: Latex学习笔记-序 IEEE模板中Section的编号是罗马数字,要是改投其他刊物的话可能得用阿拉伯数字,所以可以在导言部分做如下修改(放在导言区宏包调用之后): \ ...

  2. Latex笔记(一)—— 复杂表格的制作

    引言 表格是论文写作中的重要一环,尤其是较为复杂的三线表的制作. 在网上寻找了很久发现没有比较综合的解释方法,因此将查阅的关于复杂三线表的制作的资料总结成了一篇笔记. 由于笔记是用Latex写作的,因 ...

  3. 【LaTeX笔记12】Latex分栏布局及模板使用

    经过三天的学习,记录了12个笔记,所有的代码已经上传到GitHub. 1 分栏布局 分栏布局是我们阅读文献经常见到的排版布局,下面给一种分栏布局的基本方法. % 导言区 \documentclass[ ...

  4. $\LaTeX$笔记:首字下沉

    $\LaTeX$系列根目录: Latex学习笔记-序 首字下沉 \IEEEPARstart{W}{ith} ,第一个参数W会变大,占用两行,第二个参数"ith"变会大写. 如代码 ...

  5. 【LaTeX】Lyx/LaTeX笔记01

    list list中RETURE默认是新起一个新的item,Ctrl-Return可以继续在当前item中新起一行. 快捷键 快捷键 描述 Alt-P 0-6 Part, Chapter, Secti ...

  6. 奇怪的 Markdown / LaTeX 笔记

    记一下日常见到的一些奇怪的 Markdown / LaTeX 用法... Markdown LaTeX LaTeX 数学 1. 运算符 1.1 造运算符: a \operatorname{sin} c ...

  7. 个人LaTeX笔记(九)

    2月20日更 第十一节:LaTeX中的参考文献-BibTeX 这节课讲了参考文献的引用,很有用啊.不过引用方法分为两种首先提出一种: (参考文献样例均来自视频教程,如有侵权等问题请联系删除) \doc ...

  8. LaTex笔记:参考文献、常用公式符号、图表、注释

    目录 一.参考文献 二.公式 快捷处理方法: 小细节:斜体和正体 三.图片 四.表格 五.算法 六.注释 一.参考文献 借助bib文件生成!BibTex相当于是存放所有参考文献的容器,可以无序存放.首 ...

  9. 读书笔记《Linux那些事儿之我是USB》

    第一篇:Linux那些事儿之我是USB Core USB诞生于inel 产生是为了解决前期计算机并口串口的问题,实现一种解决速度,扩展性,易用性的通信方式. 速度:usb2.0高速模式,480MB/s ...

最新文章

  1. MySQL 语句的执行顺序
  2. ABCpdf.NET 的简易使用指南
  3. Qt中TCP服务端编程
  4. Centos 6.5 64位双网卡绑定
  5. hdu-4825(01字典树)
  6. 2021牛客暑期多校训练营3 I Kuriyama Mirai and Exclusive Or 差分 + 二进制分治
  7. 深度学习(二十五)基于Mutil-Scale CNN的图片语义分割、法向量估计
  8. virtual memory exhausted: Cannot allocate memory Linux虚拟内存不足,扩展虚拟内存的解决办法
  9. java框架ssm面试题2016_Java面试-框架篇(SSM-SpringMVC)
  10. Java自学要多久?
  11. 最小二乘法系统辨识小结
  12. 大数据与云计算概论简介
  13. 算法学习-求平方根函数
  14. OS - freeRTOS vs Linux
  15. 利用串口对 89S 系列单片机编程
  16. Opencv实现去除背景留下前景
  17. 怎样将手机视频转换成图片?手机怎么把视频做成gif动图?
  18. B站品牌营销!寻找优质UP主内容共创
  19. 高质量PM,都用哪些优质的开源项目管理工具
  20. 获得打气筒的三种方式

热门文章

  1. 金融风控实战——特征工程上
  2. 机器学习Sklearn实战——线性回归
  3. Python基础知识(第九天)
  4. Python基础知识(第八天)
  5. Python实现基于朴素贝叶斯的垃圾邮件分类 标签: python朴素贝叶斯垃圾邮件分类 2016-04-20 15:09 2750人阅读 评论(1) 收藏 举报 分类: 机器学习(19) 听说
  6. 点击按钮测试用例标题_怎么写测试用例?
  7. Elasticsearch-06 Spring Boot 2.0.9整合ElasticSearch5.6.16
  8. Java学习笔记(三)--Java主类结构
  9. java shape用法_Java PShape.scale方法代码示例
  10. 学习笔记——matplotlib学习