mweights.styとNFSS2の更新 (1)ーmweights.styが解決する問題

# はじめに

（例によってちょこちょこ修正しています．タイトル変えました）

（注意）：LaTeX2e 2020/02/02がリリースされ，そこではmweights.sty相当の機能が組み込まれています．さらに`\usepackage{mweights.sty}`を明示したとしても実際にはmweights.styは読み込まれません．したがって，mweights.styを前提としたコードは正しく動かない可能性がありますので注意してください．



LaTeX2e 2020/02/02ではNFSS2の構造が大きく変わります．大きく変わるといっても内部機構が差し変わるようなものではなく，一部は内部に踏み込んだものはありますが，ほとんどがインターフェース的な部分ではあります．とはいってもフォント周りを調整するときには知っておかなければいけません．

一次情報はltnews31.pdfやsouce2e.pdfを参照ください．

ここではltnews31.pdfで
~~「Extending the series management of NFSS」~~
「Font series defaults per document family」[^A]
と題されている節のアイデアであるmweights.styとそれにより解消される現象と，それらのベースになっているNFSS2の`\rmdefault`などの使われ方について考えます．

[^A]: 思いっきりコピペ間違えてましたorz．LaTeX2e 2020/02/02のフォント周りの更新の中で，私的にやばそうだと思っているのはltnew31.pdfの「NFSS2の改良」の項目の最初の三つで，本稿はその三つ目の件です．TeX Forumで最初に目にしてなんか起こりそうだと思ってコードを読み始めたのが本稿の発端です．

ちなみに，脱線しまくりで本題までなかなかたどり着かなく長いうえに，しかも(1)です．「ダカラナニ」感とか「ダレトク」感が満載であることをここに宣言します．

なお，TeXのtfmに関する基礎的な知識と，NFSS2に関する基礎的な知識，ざっくりいってfdファイルを読むことができるくらいの予備知識を仮定します．また，数式フォントに関しては触れませんし，和文フォントも扱いません．さらに，引用するLaTeX2eコードは特に断りがない限りLaTeX2e 2019/10/01 patch level3のものです．さらにencodingに関してはOT1に限定します．

# NFSS2の属性を設定・変更する

NFSS2の普通の説明は美文書作成入門といった優れた入門書にあるので，ここでは妙な部分に触れていきます．

## NFSS2の属性を初期設定したり変更したり

NFSS2の属性は，fonttext.ltxにおいて

```tex
%%空行およびコメントはkbhondaによる
%%以下，日本語交じりのコメントは同じ
\newcommand\encodingdefault{OT1}%%encodingのデフォルト

\newcommand\rmdefault{cmr}%%roman familyのデフォルト
\newcommand\sfdefault{cmss}%%SansSerif familyのデフォルト
\newcommand\ttdefault{cmtt}%%typewriter familyのデフォルト

\newcommand\bfdefault{bx}%%boldface seriesのデフォルト
\newcommand\mddefault{m}%%medium seriesのデフォルト

\newcommand\itdefault{it}%%italic shapeのデフォルト
\newcommand\sldefault{sl}%%slant shapeのデフォルト
\newcommand\scdefault{sc}%%SmallCaps shapeのデフォルト
\newcommand\updefault{n}%%up (normal) shapeのデフォルト

\newcommand\familydefault{\rmdefault}%%familyのデフォルト
\newcommand\seriesdefault{\mddefault}%%seriesのデフォルト
\newcommand\shapedefault{\updefault}%%shapeのデフォルト
```

という形で初期設定されています．見てわかる通り，これらは単に文字列をマクロにしているだけです．これらのマクロは，`\process@table`というマクロで次のように用いられます．

```tex
\def\process@table{%
%%%%ざっくり省略：フォント設定のエラーチェックが入っています
    \fontencoding{\encodingdefault}%
    \fontfamily{\familydefault}%
    \fontseries{\seriesdefault}%
    \fontshape{\shapedefault}%
 \everyjob{}%
}
```

名前の通り，encoding，family，series，shapeのデフォルトをそれぞれのデフォルト値に設定します．ただし，実際は

```tex
\DeclareRobustCommand\fontencoding[1]{%
%% encoding #1 の存在チェックが入っている
      \edef\f@encoding{#1}%
%% encodingの更新に関する処理が入っている
%%（ここでは更新そのものはしない）
}
\DeclareRobustCommand\fontfamily[1]{\edef\f@family{#1}}
\DeclareRobustCommand\fontseries[1]{\edef\f@series{#1}}
\DeclareRobustCommand\fontshape [1]{\edef\f@shape{#1}}
```

となっており，何に変更するか（変更後の値）を記録するだけです．そして`\begin{document}`の中で`\process@table`が実際に実行されて，`\f@family`などが更新され，そのあとで`\begin{document}`の中で実行される`\normalsize`の中の`\selectfont`[^1]によって初めてフォントが変更されます．

[^1]: 実際は`\normalsize`は`\@setfontsize`を用いており，その中で`\selectfont`が用いられています．この`\selectfont`がフォントの変更を実際に行います．

### classファイルにも\normalsizeがあるんだけど

本当はもう少し複雑です．`\begin{document}`よりも前に，classファイルが読み込まれていて，その中で`\normalsize`が実行されて，そのときに`\selectfont`が実行されます．そのため，LaTeX2eではあらかじめ

```tex
\fontencoding{OT1}%%fonttext.ltx で

\fontfamily{cmr}%%以下はlatex.ltxで
\fontseries{m}
\fontshape{n}
\fontsize{10}{10}%%サイズ10pt，送り10pt
```

と宣言されています．これらの値が`\familydefault`などの値と一致していることに注意して下さい．これによって，`\fontfamily`などをclassファイル内で明示していなくても，フォントの初期化がclassファイルで一度行われます．

つまり，実は同じ処理がここと`\begin{document}で二回実行されています[^2]．

[^2]: これより，classファイルやプリアンブルで`\fontfamily{ptm}\selectfont`などとしても`\begin{document}`で`\familydefault`などでデフォルト（`\rmdefault`など）に戻されることがわかります．これ案外知られていないように思いますが，こんな妙な状況にはそうそう遭遇できません．



### プリアンブルで（簡単に）上書きできる－times.sty

フォントの初期化が二回行われているのは無駄のようですが，どちらか一方がもしなかったとしたら不便です．もしclassファイル内での設定がなければ，基準のフォントに関する処理に制限が発生します．また，`\begin{document}`内で初期化がなければ，プリアンブルでのフォント変更が面倒になります．

例として，times.styを引用します

```tex:times.sty

\ProvidesPackage{times}%
[2005/04/12 PSNFSS-v9.2a
(SPQR)
]
\renewcommand{\sfdefault}{phv}
\renewcommand{\rmdefault}{ptm}
\renewcommand{\ttdefault}{pcr}
\endinput
```

times.styは大変昔からあるパッケージで，本文の書体をAdobeの基本書体，RomanをTimesRoman family，SansSerifをHelvetica family，TypewriterをCourier familyに変更します．ただし，このパッケージの中身はマクロを三つ定義しているだけで，その定義も単なる置き換えです．

しかし，`\rmdefault`などを書き換えているので，プリアンブルの終了後，`\begin{document}`の中で`\familydefault`などを経由しての再初期化で実際にフォントが変更されます．この再初期化がなければ，プリアンブルでのフォントの変更はもっと複雑なことになる上に同様のフォント変更パッケージで類似のコードが書かれることになって無駄が多くなります．

### フォントを変更する

times.styで示されているように，LaTeX2e（もしくはclassファイル）のデフォルトのフォント設定を変えるには，Roman（rm），SansSerif（sf），Typewriter（tt）のそれぞれのデフォルト（`\rmdfault`）を再定義すればよいわけです．

LaTeX2eではfamilyとしてはrm/sf/ttの三つが用意されており，
times.styはこの三つのfamilyのデフォルトを変更しています．一方，先に引用したように，他のデフォルトが定義されています．series用にはbf/mdのデフォルトが，shape用にはit/sl/sc/upのデフォルトです．そして，

```tex
\newcommand\seriesdefault{\mddefault}%%seriesのデフォルト
\newcommand\shapedefault{\updefault}%%shapeのデフォルト
```

として，seriesとshapeそのもののデフォルトが定められています．結局のところ，`\begin{document}`の開始時点での\encodingdefalut/\familydefault/\seriesdefault/\shapedefaultとサイズ\f@size（`\normalsize`で与えられる）で文書開始の際のフォントが決まります．

これを踏まえると，times.styではfamilyに与えられるデフォルト（`\rmdefault`，`\sfdefault`，`\ttdefault`）のみを変えていますが，例えば，seriesに与えられるデフォルト`\mddefault`を書き換えればseriesも変えることができます．shapeに関しても`\updefault`の書き換えで変更できます．

もちろん，例えば`\mddefault`の形ではなく，直接`\seriesdefault`の方を書き換えることもできますが，そうするとフォント変更のマクロでの整合性がとれなくなってきます．seriesを例にすると，seriesのユーザ用の変更マクロは二つ，`\bfseries`と`\mdseries`であり．以下のように定義されています．

```tex
\DeclareRobustCommand\bfseries
        {\not@math@alphabet\bfseries\mathbf
         \fontseries\bfdefault\selectfont}
\DeclareRobustCommand\mdseries
        {\not@math@alphabet\mdseries\relax
         \fontseries\mddefault\selectfont}
```

このように，`\bfseries`はフォントを`\bfdefault`に，`\mdseries`は`\mddefault`にするように定義されています．もし，`\seriesdefault`を`\mddefault`と違う値にしてしまったら，`\mdseries`で期待される「標準のseriesに戻す」という働きがなくなってしまい混乱が起こるでしょう．もちろん，`\mdseries`は「中くらいの太さ」という意味であって標準ではないという意見もあるかもしれませんが，LaTeX2eでは実質的に「標準に戻す」よう実装されているので混乱を起こすでしょう．

同様に，他のフォント変更マクロもそれぞれのデフォルトを使うように定義されています．latex.ltxの中のltfssini.dtxというセクションにこれらのフォント変更マクロは定義されているので参照してください．

以上より，フォントを変更する場合には先にあげた`\rmdefault`のような`\XXdefault`の形の九つのマクロ

```tex
\rmdefault %cmr  roman familyのデフォルト
\sfdefault %cmss SansSerif familyのデフォルト
\ttdefault %cmtt typewriter familyのデフォルト

\bfdefault %bx boldface seriesのデフォルト
\mddefault %m  medium seriesのデフォルト

\itdefault %it italic shapeのデフォルト
\sldefault %sl slant shapeのデフォルト
\scdefault %sc SmallCaps shapeのデフォルト
\updefault %n  up (normal) shapeのデフォルト
```
を適切に定義するとLaTeX2eに準備されている，`\XXfamily`や`\textXX`などのフォント変更マクロの枠組みにそのままのることができます．これは便利なのでそのように作られているパッケージもあるでしょう．


# フォント変更の問題点

昔はフォント自体が高価なこともあって，それほど種類が多くありませんでした．大体，一つのfamilyについて，seriesはbxとm，shapeはnとitがそろっていればそれで問題ありませんでした．たとえば，

- Times ptm
    - Times-Roman      (ptm/m/n)
    - Times-Italic     (ptm/m/it)
    - Times-Bold       (ptm/bx/n)
    - Times-BoldItalic (ptm/bx/it)
- Helvetica phv
    - Helvetica-regular    (phv/m/n)
    - Helvetica-Italic     (phv/m/it)
    - Helvetica-Bold       (phv/bx/n)
    - Helvetica-BoldItalic (phv/bx/it)

というような感じです[^3]．familyよりも下の属性の値そのものは同じでかまわない，平和な状況です．もちろん商品としては同一のfamilyでたくさんの種類が実際にはあったのですが，それほど使う必要はありませんでした．

[^3]: 厳密には(ptm|phv)/bx/(n|it)ではなく(ptm|phv)/b/(n|it)が定義されて，bxのものはbに変換されるという定義がなされていますが，bxが定義されているものとみなしています．

ところが，最近はフリーであっても一つのfaimlyでも種類が豊富で品質の良いものがたくさんでてきましたし，商用のフォントでも一つのfamilyでたくさんの種類があるものがたくさんがでてきました．なにより凝ったデザインの印刷物も増えて，たくさんのフォントを使う機会が格段に増えました．

例えば，Futuraというフォント[^4]では，ベンダーにもよりますが，Bitstrems社のものでは19種類が存在します．そのうちFuturaBlackBT-Regularはデザインの系統が違うのではずします．

[^4]: 東京メトロの駅名標で丸の中に「M10」のように書かれている路線名（を表すアルファベット）と駅番号のアイコンのフォントがFutura-Boldです．

FuturaBT-Light　              (bfu/l/n)
FuturaBT-LightItalic　        (bfu/l/it)
FuturaBT-Book                 (bfu/m/n)
FuturaBT-BookItalic           (bfu/m/it)
FuturaBT-Medium               (bfu/md/n)
FuturaBT-MediumItalic         (bfu/md/it)
FuturaBT-Bold                 (bfu/b/n)
FuturaBT-BoldItalic           (bfu/b/it)
FuturaBT-Heavy                (bfu/h/n)
FuturaBT-HeavyItalic          (bfu/h/it)
FuturaBT-ExtraBlack           (bfu/eb/n)
FuturaBT-ExtraBlackItalic     (bfu/eb/it)
FuturaBT-LightCondensed       (bfu/lc/n)
FuturaBT-MediumCondensed      (bfu/mc/n)
FuturaBT-BoldCondensed        (bfu/bc/n)
FuturaBT-BoldCondensedItalic  (bfu/bc/it)
FuturaBT-ExtraBlackCondensed  (bfu/ebc/n)
FuturaBT-ExtraBlackCondItalic (bfu/ebc/it)
(FuturaBlackBT-Regular)

NFSS2での属性の値は，なるべく実際のフォントを反映するようほうがよいですので，上記のカッコ内のようにしてみました[^5]．

[^5]: BookとMediumの扱いが悩ましいですが，規範との整合性は完全にとれるわけではないので気にしないことにします．


tfmを準備して，fdファイルot1bfu.fdを上の一覧に従って用意したとします．ここが一番手間がかかるのですが，話題が違うので割愛します．

悩ましいのはここからです．FuturaはSansSerifのフォントであることを考慮して，以下のようなfuturaBT.styを作ってみます

```tex:futuraBT.sty
\renewcommand{\sfdefault}{bfu}%%% sfをFuturaにする

\renewcommand{\bfdefault}{b}  %boldfaceをFutura-Boldに
\renewcommand{\mddefault}{md} %mediumをFutura-Mediumに

%\renewcommand{\itdefault}{it} %italicはそのままに
%\renewcommand{\sldefault}{sl} %slantはそのままに
%\renewcommand{\scdefault}{sc} %SmallCapsはそのままに
%\renewcommand{\updefault}{n}  %upはそのままに
```

それらしい組み合わせにしてみましたが，`\mddefault`と`\bfdefault`の変更が問題を引き起こします．

`\rmdefault`は変えていないのでRomanはcmrのままです．ということは，`\bfdeault`をbにすることで，`\bfseries`はcmr/m/nをcmr/b/nに変更します．Futuraの定義のせいで他の書体の字形が変わってしまいます．さらに，`\bfseries\itshape`に至っては，cmr/m/nがcmr/b/itになりますが，cmr/b/itは未定義なのでcmr/b/nになってしまいます．イタリックがそうではなくなってしまいます．これは大変によろしくないのです．

## 対症療法的な回避策

この問題は`\bfdefault`などをそもそも変更しなければ起きません．fdファイルを作る際に，同一の文書で使われる他の書体を考慮してNFSS2の属性値の割り当てを考えればいいわけです．Futura-Mediumはbfu/m/n，Futura-Boldはbfu/bx/nにという感じです．

ただ，このような属性値の調整をするとfdファイルとして外部に出すのは危険です．これくらいならなんとかなるかもしれませんが，もっと大掛かりな調整（bxにFutura-ExtraBoldをあてるとか）をしてしまうと属性値と実体の乖離はごまかせないでしょう．なんにせよ対象となる文書の他の書体の状況に依存することになるので，fdファイルの内容も含めてその文書のclassファイルに埋め込むか，せめて作業フォルダの中に入れて不要な流用を避ける必要があります．特定の状況に依存したものが自動的に読み込まれると厄介事を誘起します．

それならば，属性値はきちんと実体を反映したものにしておけばいいとなりますが，そうするとフォントの変更マクロの方に影響が出ます．LaTeX2eの標準である`\bfseries`などを使うと期待したフォントにならないことが起こりえます．ですのでこの場合は，フォント変更マクロそのものを新設することになります．

このマクロの新設に関してはNFSS2のフォント変更マクロ類を参照して分かりやすい名前で作れば，手間はかかりますが容易なルーチンワークです．おそらくスクリプトで自動生成できるくらいのものです．ただし，この場合，使用するフォントごとに違う名前のマクロができたり，使用方法が自然ではないマクロになって使いにくくなるということが起こります[^6]．

[^6]: 私は文書ごとにちがうclassファイル（もしくは追加の設定）を作る必要があるので，文書ごとに追加フォントのために新しい変更命令を作っています．属性値は実体に合わせておいて，変更マクロの実体は`\useroman`で決め打ちにしてしまうことが多いです．組み合わせの問題でどうしても代替が起こってしまう場合は，代替の警告とその場所での望ましいフォントを考えて，代替が起きず，かつ望ましいフォントが現れるように`\DeclareFontShape`を追記します．基本的にはフォントの代替が一切ないように調整します．用途が特定されているので汎用性をあきらめているともいえます．



どうするにしても，結局は対症療法である面が大きいので汎用性が失われます．この問題への一つの解決策がmweights.styです．実はここで問題のすり替えをしています．何気にseriesだけに限定していることですが，ちょっと実験と考察が追い付かないのと話題が発散するので，ここでは無視しておきます．

では，mweights.styがどのような手を繰り出しているのかは次回．