零、
我就不寫成《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
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。