LaTeX 笔记:NFSS 那点事儿
零、
我就不寫成《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 的字符)。正確的寫法相當于這樣:
\expandafter 和 \csname ...\endcsname 的用法如果不明白,趕緊去看書。?
也就是說,真實的字體命令,是由這些坐標拼起來構建的。不同的坐標會構造出來不同的字體命令,你每切換一次坐標,很可能就要生成一個新的字體命令。
三、
真的就是把字體坐標拼起來這么簡單?當然不止,LaTeX 需要額外的信息找到上面代碼里等號后面要賦值的字體名。把坐標與字體名稱建立映射的東西,就是每個字體宏包必不可少的 .fd 文件。這個放到后面穿插著說。
四、
現在要提到 NFSS 里的一個比較核心的命令——\selectfont。在 \bfseries \itshape 或者 \small \large 等的定義里都有它。你光用 \fontseries{bx} 或者 \fontsize{10}{12} 改坐標還起不到改字體的作用,它只是把坐標信息存到了一些內部宏,如 \f@encoding \f@series 等。\selectfont 才是改變字體的實際操作。
那么 \selectfont 主要干了些啥呢??
定義里,前兩行先不細看,它的作用是處理行距信息。行距有兩種改法:\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 那点事儿的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图普科技招聘有关深度学习的解题?
- 下一篇: 如何用 C++ 在 10 行内写出八皇后