「XeTeX ベースの npTeX」の話（TeXConf 2023 補足）

:::note info
これは「[TeX & LaTeX Advent Calender 2023](https://adventar.org/calendars/9305)」の 15 日目の記事です．14 日目は [zr_tex8r さん](https://qiita.com/zr_tex8r/items/05dd9958169cd844975d)，16 日目は [wtsnjp さん](https://blog.wtsnjp.com/2023/12/16/latex-bot/)です．
:::

先日の [TeXConf 2023](https://texconf2023.tumblr.com/) で「npTeX の開発（方針）について」（[スライド PDF](https://github.com/h-kitagawa/presentations/blob/main/tc23nptex.pdf)）という講演をしました．そこで出てきた「XeTeX ベースの npTeX」について補足します．

## 背景
### 和文文字トークン
ここ数年，「**(u)pLaTeX は滅亡する！**」という話を聞いたことがあるかもしれません．詳細は
 * aminophen さんの「[pLaTeX が本格的にやばいかもという話](
https://acetaminophen.hatenablog.com/entry/2021/06/18/022108)」(2021-06-18)
 * ZR さんの「[”pLaTeXがヤバい”問題を一気に解決する話（※ただし画期的）](https://zrbabbler.hatenablog.com/entry/2022/08/08/080002)」(2022-08-08)

などに譲りますが，その原因の一端には
 > 海外の TeX 開発者（LaTeX チームも含む）にとって，(u)pTeX の「和文文字トークン」という機能は異質であり，多くのパッケージが和文文字トークンの存在を考慮して書かれていない

:::note warn
もちろん，pTeX 系列利用者にとっては和文文字トークンはよく慣れ親しんでいるものです．
:::
 
ことが挙げられます．もうちょっと具体的に言うと，次のようになるでしょうか．
 * (u)pTeX は，欧文部分だけを見れば TeX82 や pdfTeX と同じく「8 ビットエンジン」である．すなわち，欧文文字としては 0--255 の範囲の文字コードしか扱えないし，欧文フォントも 8 ビットのものしか扱えない．
 * その一方，和文文字トークンは文字コードが 256 以上になりうる[^1]
 * 欧文文字トークンは「0--15 の範囲のカテゴリーコード (\catcode)」と「文字コード」の組であるが，和文文字コードは \catcode を持たない
   * とくに，`~` のような「アクティブ文字」が和文ではできない

[^1]: pTeX では和文文字トークンの文字コードは確実に 256 以上ですが，upTeX では文字コードが 128--255 の範囲の欧文文字トークンも存在します．

しかし，現状の日本語 TeX 開発コミュニティには，既存のパッケージ（とくに，[LaTeX3](https://www.latex-project.org/latex3/) の内部処理）を和文文字トークンに満足に対応させるだけのリソースはありません．
そのため，**「本家 LaTeX についていけずに(u)pLaTeX が動かなくなる」可能性**が日に日に高まっているわけです．

### npTeX
2022 年秋から，前項で述べた状況を TeX/LaTeX マクロレベルではなく，エンジンレベルで状況を改善しよう，という動きが始まりつつあります（「[npTeX] 欧文トークンに似せたCJKトークン」，[tex-jp-build/#150](https://github.com/texjporg/tex-jp-build/issues/150)）．

すなわち，**npTeX** という名称の，
 * LaTeX 本体の改変に伴う，npLaTeX の保守が「楽に」行える
 * 従来からある各種の upLaTeX 用クラスファイルや，CTAN にある各種パッケージが「最小限の変更で」そのまま動く
 * 内部コードは Unicode 固定であり，Unicode 1 文字から 1 トークンを生成する．トークン段階での和文・欧文の区別は必須ではない

エンジンを作成しよう，というものです．

:::note
npTeX という名称は，[t-tk さんのコメント](https://github.com/texjporg/tex-jp-build/issues/150#issuecomment-1292121824) に由来するもので，
> new pTeX, next pTeX, novel pTeX, notable pTeX でかつ nippon pTeX, nihongo pTeX または nippon TeX

という意味が込められているそうです．

TeX/LaTeX の慣習に従い，npTeX 上で動く (p)LaTeX フォーマットを npLaTeX と呼称することにします．
:::

……とは言うものの，npTeX については「ほぼ名前しか決まっていない」状況です．開発の方針として
 * 現状の e-upTeX から文字トークンの扱いを変更する
 * XeTeX に pTeX 相当の和文組版機能を載せる

の 2 つが考えられますが，この記事はとくに後者について紹介するものです．

:::note warn
この記事には個人的な独断と偏見がかなり含まれています，注意してください．
:::

## XeTeX ベースの npTeX
「XeTeX に pTeX の和文組版を載せるのはどうか」という案は，2022 年秋に aminophen さんが Slack で発言をされていたのがきっかけです（残念ながら北川の手元にログは残っていません）．その当時は私は反対の立場だったのですが，2023 年夏にまとまった時間が取れたのでとりあえず実装してみたところ，「無理ではないかな」と思うようになりました．

:::note
[h-kitagawa/texlive-source の nptex_xt_sandbox_01 ブランチ](https://github.com/h-kitagawa/texlive-source/tree/nptex_xt_sandbox_01)あたりでいろいろいじっています．「npTeX の開発（方針）について」のスライド PDF もこれで作成しました．
:::

:::note warn
マニュアルはまだ作っていません．試したいなら，大雑把に言うと次のようになるでしょうか．
 1. `nptex` バイナリを `./Build --enable-nptex` として作成，`platex` バイナリ等のある場所に `nptex`, `nplatex` (, `nplatex-dev`) という名前でおく
 2. `texk/web2c/nptexdir/texmf` にある `texmf.cnf`, `fmtutil.cnf` を `$TEXMFLOCAL` にコピー
 3. `texk/web2c/nptexdir/texmf` の中身を `$TEXMFLOCAL/tex/nptex` 以下におく
 4. mktexlsr とか，フォーマット作成とかを頑張る．
:::

### XeTeX ベースがなぜ「無理ではない」か
まず，XeTeX がすでに Unicode TeX として，すなわち「Unicode 1 文字が 1 トークン」というエンジンとして成熟していることが挙げられます．

次に，XeTeX は「e-TeX からできるだけ改変はせず，OpenType フォント（など）の扱う処理を上乗せしている」実装であることで，これが作業を楽にしています．具体的に述べると，以下のような点です．
 1. 「XeTeX は PDF ファイルを出力する」とよく言われているが，実際に XeTeX が出力するのは DVI ファイルの変種[^2]である．「PDF ファイルを出力している」ように見えるのは，裏で xdvipdfmx にパイプで渡しているからである．
 1. OpenType フォントで組まれた単語や文字は，内部では `native_node`, `glyph_node` という **whatsit ノードの一種として**表現される．

[^2]: XDV ファイルといい，XeTeX では実行時に `-no-pdf` オプションをつけることで生成されます．DVI ファイルからどこが拡張されているかは XeTeX や xdvipdfmx のソースコードを読みながら推測するしかありませんが，OpenType フォントを扱うための命令が追加されているだけのようです．

### 開発方針
以下のような方針でゆったり作業しています：
 1. e-TeX→e-upTeX の差分が適用できる部分はできるだけそのまま適用する．
 1. 日本語フォントは従来の JFM 経由利用のみとする．
 1. 内部パラメータを適切に設定すれば，npTeX 拡張部分をすべて無効化し，素の XeTeX と同じ動作をさせるようにする．

まず，1. は e-upTeX との互換性をできるだけ確保するためで，
 * upLaTeX 用ソースからの改変量をできるだけ少なくする（要改変箇所は`\kcatcode` 周りぐらいに留めたい）[^3]
 * 欧文部分では OpenType フォントを使える
 * 和文フォント設定は，dvipdfmx 経由での設定が使える

という，「pTeX ユーザにとっつきやすい Unicode LaTeX」を目指すものです．「単位 zw, zh, Q, H」などもそのまま使えます．

[^3]: LuaTeX-ja はマクロパッケージでしかないので，どうしても「単位 zw, zh, Q, H がそのままでは使えない」「kanjiskip などの和文組版用のパラメータについて，設定と取得で別命令になっている」などといった乗り換えの不便さがあります．まあ，最初から LuaTeX-ja で書いていれば気にならないですが．

次の 2. は，「OpenType フォントで和文も組めるようにする」には実装の手間と効果が釣り合わないように感じたためです．LuaTeX-ja との棲み分けという面もあります．

最後の 3. は，海外の開発者への説明の手間を考えてのことです．
 * 「大まかには XeTeX だが，○○の機能は削除してある」というエンジンを XeTeX 互換と表明する（`\sys_if_engine_xetex:p` が真）ことは望ましくありません．

 * 実際のパッケージのコードを見ると，**次の 4 つが全部同値であるという想定で書かれているものが目に付きます**．
    * LuaTeX か XeTeX である
    * \Umathcode プリミティブが定義済みである
    * Unicode 1 文字から 1 トークンができる
    * （本文の）欧文フォントは Unicode フォントである

 * そのため，「npTeX は LuaTeX, XeTeX とは全く別の新エンジンですよ」と表明したとすると，各パッケージの開発者に npTeX のことを説明して回らないといけません．

 * フォーマット未読込時の iniTeX 段階では，いくつか追加のプリミティブがある以外は XeTeX と区別がつかない，というのが理想だと思います．日本語用の設定は nptex/nplatex フォーマット作成時に行ってしまえばよいでしょう．

### 「DVI 互換モード」の必要性？
現在ではどれほど行われているかわかりませんが，かつては DVI ファイルを直接 dviout や xdvi で閲覧したり，dvips 経由で PostScript に変換するということがよく行われていました．

XeTeX では事実上「xdvipdfmx 経由で PDF を作成する」しか出力の使い道がないわけですが，前段落に述べたような「PDF 変換以外の使い道」を考慮するのであれば，dvips 等に通せるような DVI を出力する「DVI 互換モード」の実装の意義が出てきます．そこでは，OpenType フォントといった XeTeX 特有のいくつかの拡張や，\XeTeX... というプリミティブ類は使えなくなります．

:::note warn
なお，「DVI 互換モード」では，エンジンとしては「e-upTeX ベースの npTeX」とほとんど変わらないものになります．LaTeX 上での扱いでは要検討ですが．
:::

## 和文と欧文の区別
「和文文字トークン」「欧文文字トークン」という区別をつけないのであれば，1 つの文字トークンから「欧文フォントで組まれる」「和文フォントで組まれる」方法を別に用意しないといけません．

ここでは，各文字コードごとに \cjkxcode という非負整数値を使って区別させる方針をとりました．
従来 \kcatcode では「Unicode ブロックごとに」「複数の属性をいっぺんに」制御していたのを，文字コード単位でより細かく制御できるようにしています．

文字コード *c* に対応する \cjkxcode の値を，2 進法で $\ldots azyx_{(2)}$ と書くと，これらの意味は以下の通りになります：
| ビット | 1 の効果 |
|:-|:-|
| *x* | 文字コード *c* の和文文字ノードの直後には \jcharwidowpenalty が**挿入されない**|
| *y* | 文字 *c* の直後の改行は空白を挿入しない |
| *z* | 和文文字扱いする（文字コード *c* ，\catcode 11, 12 のトークンから和文文字ノードを生成する） |
| *a* | *z*=0 のとき，数式中でも和文文字扱いする |

大雑把に言うと，次のように対応します：
|upTeX での \kcatcode | \catcode | \cjkxcode mod 8|
|:-|-:|-:|
| 16 (kanji), 17 (kana) | 11 | 6 |
| 18 (other_kchar) | 12 | 7 |
| 19 (hangul) | 11 | 4 |

:::note warn
互換性のため，\kcatcode による「値の取得」はできるようにしています．\catcode, \cjkxcode) の組から，「それっぽい」 15--19 の値を返します．
:::

## 欧文フォントと Unicode による直接入力
:::note warn
この節の内容は npLaTeX というより，XeLaTeX, LuaLaTeX といった「Unicode LaTeX」全般に関わるものです．
:::
### OpenType フォントを使う場合
Unicode LaTeX では欧文フォントは標準で Unicode で，fontspec パッケージを利用して欧文 OpenType フォントを扱うことが出来ます．これは npLaTeX でも変わりません．

```tex
%#!nplatex
\documentclass{utarticle}
\usepackage{fontspec}
\setmainfont[
  Extension=.otf,
  UprightFont=*-Regular, BoldFont=*-Bold,
  ItalicFont=*-Italic, BoldItalicFont=*-BoldItalic
]{LibertinusSerif}
\setsansfont[
  Extension=.otf,
  UprightFont=*-Regular, BoldFont=*-Bold,
  ItalicFont=*-Italic,
]{LibertinusSans}

\makeatletter
% U+0400--U+052F （キリル文字とキリル文字補助）を欧文扱いに
\@tempcnta="400 
\loop\ifnum\@tempcnta<"530
  \cjkxcode\@tempcnta=0 \advance\@tempcnta by1
\repeat
% U+2000--U+206F （一般句読点）を欧文扱いに
\@tempcnta="2000
\loop\ifnum\@tempcnta<"2070
  \cjkxcode\@tempcnta=0 \advance\@tempcnta by1
\repeat
\makeatother
\begin{document}
% 文章は ZR さんの「BXfltsrc パッケージ」解説 (http://zrbabbler.sp.land.to/pxfltsrc.html)
% から適宜引用．
“¿But aren’t Kafka’s Schloß and Æsop’s Œuvres often naïve vis-à-vis the dæmonic
phœnix’s official rôle in fluffy soufflés?” \par

\sffamily\gtfamily
日本語の外来語の中にロシア語を起源とするものがある。
ペレストロイカ（перестройка）やウォッカ（водка）、
ボルシチ（борщ）といったロシア（ソ連）に直接関連するものは除いても、
イクラ（икра；魚卵）やノルマ（норма）は元はロシア語で、
また「インテリ」は英語のintelligence ではなくロシア語のинтеллигенция）の省略形である。
\end{document}
```

ここでは紹介しませんが，unicode-math パッケージを用いて OpenType 数式フォントを用いることも可能です．

### 8 ビットフォント＋伝統的な「`\"{a}`」記法
Unicode LaTeX では 8 ビットフォントの使用自体が全く不可能なわけではありません．
 * fontenc パッケージで明示的に 8 ビットのエンコーディング（T1 など）を指定し，
 * アクセント文字等の入力では Unicode 直接入力でなく，古くからの `\"{a}` といった出力命令を使う

限りでは，正常に動作します．
```tex
%#! pdflatex, xelatex, lualatex, nplatex
% uplatex では適切に \kcatcode の設定が必要
\documentclass{article}
\usepackage[T1]{fontenc}
\begin{document}
``?`But aren’t Kafka’s Schlo\ss\ and \AE sop’s \OE uvres often na\"{\i}ve vis-\`{a}-vis the d\ae monic
ph\oe nix’s official r\^{o}le in fluffy souffl\'{e}s?” \par
\end{document}
```

:::note
数式フォントに 8 ビットフォントを使うことは問題ありません．
私（北川）が「仕事」で TeX を扱うときは LuaTeX-ja を専ら使っていますが，地の文が OpenType フォントである一方，数式フォントは 8 ビットフォントの newtxmath パッケージを使っています．
:::

### 問題：8 ビットフォント＋Unicode 直接入力
**問題になるのは，本文で 8 ビットフォントを使いながら，Unicode による直接入力を行った場合です．** 以下のようなソースは，pdfTeX や（\kcatcode を適切に設定した）upLaTeX では意図通りに動きますが，Unicode LaTeX ではそうではありません．

```tex
\documentclass{article}
\usepackage[T1]{fontenc}
\begin{document}
“¿But aren’t Kafka’s Schloß and Æsop’s Œuvres often naïve vis-à-vis the dæmonic
phœnix’s official rôle in fluffy soufflés?” \par
\end{document}
% Unicode LaTeX では
% `Missing character: There is no Œ ("152) in font ec-lmr10!' 等の警告
```

このようになるのは，たとえば「Œ」 (U+0152) について説明すると，
 * 非 Unicode な LaTeX では，何段階かの処理を経て出力命令 `\OE` に変換されるので，正しく出力される
 * Unicode LaTeX では，「現在のフォントの 338 (=0x152) 番の文字を出力」しようとするが，当然そのような文字はないので，何も出力されない

ためです．

<details><summary>歴史的経緯</summary>

もともと，Unicode（や，他のエンコーディング）による直接入力は inputenc パッケージが担っていた機能でした．Unicode LaTeX ではそもそも inputenc パッケージの使用が想定されていませんでした．

非 Unicode な LaTeX では [LaTeX2e 2028-04-01](https://www.latex-project.org/news/latex2e-news/ltnews28.pdf) から，`\usepackage[utf8]{inputenc}` 相当の機能が LaTeX 本体に取り入れられるようになりました．
</details>

<details><summary>Unicode LaTeX で「変換処理」が何も行われない理由を推測すると……</summary>

まず，**元々Unicodeフォントを利用が想定されている**ことが挙げられるでしょう．本文で 8 ビットフォントを使用するためには inputenc パッケージを明示的に読み込む必要があり，「わかっている人が，意図を持って」やっていることだと認識されているのでしょう．

次に，変換処理を実装しようとした場合，おそらく「該当の Unicode 文字をアクティブ化」ことになると思いますが，これはこれで違和感を生む危険があるからです．確かに，「Œ」をアクティブ化し， `\OE` に展開されるようにすれば「Œ」が正しく出力されるようになります．そうすると，`\Œuvres` という名称の制御綴をソース中に直接は書けない[^4]ことになってしまいます．
</details>

[^4]: `\ifincsname` 等を活用することで，`\csname Œuvres\endcsname` と間接的に表現することは出来るはずですが．

:::note alert
本小節の「8 ビットフォント＋Unicode 直接入力」がうまく行かないという問題は，日本語 TeX 開発コミュニティ側で解決すべき問題ではないように考えています．もし解決を目指すのであれば，LaTeX 本体に動いてもらいたいという気分があります．
:::

:::note alert
なお，この問題は元々「Unicode 1 文字から 1 トークン」に起因する問題であるので，npTeX を e-upTeX ベースで実装させる際にも避けては通れません．
:::

## 懸念点：XeTeX と pTeX の干渉
XeTeX ベースにすることの懸念点として，XeTeX 拡張と pTeX 拡張とが干渉することが考えられます．ここでは 1 つの例を示します．

### 入力エンコーディングの扱い

XeTeX では複数の入力エンコーディングをサポートするために
 * \XeTeXinputencoding \<*encoding*>：現在読込中のファイルの，以降の行の文字コードを指定する．（ZR さんの「[きょうの XeTeX (3) : \XeTeXinputencoding, \XeTeXdefaultencoding](https://zrbabbler.hatenablog.com/entry/20151030/1446222091)」に詳しい）．
 * \XeTeXdefaultinputencoding \<*encoding*>：これ以降に読み込むファイルの文字コードを指定する．
 * \XeTeXinputnormalization：入力行に対して Unicode 正規化を行うかを指定する（ZR さんの「[きょうの XeTeX (2) : \XeTeXinputnormalization](https://zrbabbler.hatenablog.com/entry/20151021/1445432229)」に詳しい）．指定値は
   * 0: 何もしない
   * 1: NFC (Normalization Form Canonical Composition) へと正規化する
   * 2: NFD (Normalization Form Canonical Decomposition) へと正規化する

という命令が準備されています．しかし TeX Live の `$TEXMFDIST/tex` 以下を検索した限りだと，\XeTeXinputencoding をまともに使っているのは
```
generic/babel/xebabel.def:    \XeTeXinputencoding"bytes"%
generic/babel/xebabel.def:    \XeTeXinputencoding"#1"%
generic/babel/xebabel.def:  \def\xebbl@stop{\XeTeXinputencoding"utf8"}}
generic/e-french/efrenchu.tex:\XeTeXinputencoding{utf8}
latex/examplep/examplep.sty:\@ifundefined{XeTeXinputencoding}{}{\XeTeXinputencoding "cp1250" }
latex/gmutils/gmbase.sty:\XeTeXinputencoding utf-8 % we use Unicode dashes later in this file.
texinfo/texinfo.tex:    \XeTeXinputencoding "bytes"  % For document root file
```

程度のようです．

一方，pTeX 系列は独自に文字コード変換の機能を備えており ([ptexenc](https://texwiki.texjp.org/?ptexenc))，
 * \epTeXinputencoding \<*encoding*>：現在読込中のファイルの，以降の行の文字コードを指定する．\<*encoding*> に指定可能な値は，基本的には `euc`, `sjis`, `jis`, `utf8` である[^5]，
   * \<*encoding*> の値が何であろうと，JIS (ISO-2022-JP) でエンコードされたバイト列は正常に読み込める．
   * UTF-8 入力においては，「かな」と「合成用濁点・半濁点」の並びが合成済の形に常に変換される．

のようになっています．

[^5]: 厳密には `BINARY` (=`jis`), `Shift_JIS`, `EUC-JP`, `UTF-8` などの値も指定できます．

いくら \XeTeXinputencoding が使われていないからといって，もし「npTeX は XeTeX 互換」と表明するのであれば無視するわけにはいきません．そこで，付け焼き刃ですが，[nptex_xt_sandbox_01 ブランチ](https://github.com/h-kitagawa/texlive-source/tree/nptex_xt_sandbox_01)で実験しているものでは，次のようにしています．
 * \epTeXinputencoding は実装していない．
 * \XeTeXinputencoding の引数の \<*encoding*> に以下の値が指定された場合は，XeTeX 由来のルーチンの代わりに ptexenc 由来のルーチン[^6]を使い，各行を Unicode にコード変換していく：
   > ```utf8_nptex, jis, sjis, euc, iso-2022-jp, shift_jis, euc-jp```
   
   上記の値が指定されたときは，\XeTeXinputnormalization の値は効力を持たない（濁点・半濁点の合成だけが行われる）．

[^6]: 正しくは，ptexenc の `input_line2` 関数を元に XeTeX の内部動作に合うように書き起こしたルーチン（`XeTeX_ext.c` 中の `input_line_nptex` 関数）です．pTeX 系列では内部バッファの型は UTF-8，XeTeX では Unicode スカラーなので，「nptexenc」とでもいうものを起こしたほうが良いかもしれません．

## まとめ
やっぱり TeX はアレ