📚

TeXの「エンコーディング」とは何だったのか

2024/12/24に公開

TeXはコンピューターで文字を扱うプログラムなので、文字を数値で表して使う。文字を数値として解釈する規則は、一般に「エンコーディング」とか「文字コード」と呼ばれる。両者は同じ意味で使われることも多いけれど、文字と数値の単純な対応表が文字コード、バイト列の解釈方法がエンコーディングという含意がそれぞれあるような気がする。以降では基本的に「エンコーディング」という表現を使う。

さて、文字に関するエンコーディングには実は次のような(互いに関係はするけれど)まったく違う2種類の側面がある。

  • ファイル中のバイト列を文字として解釈する規則としてのエンコーディング(入力エンコーディング
  • (文字を表す)数値からフォントにおけるグリフを特定する規則としてのエンコーディング(フォントエンコーディング

これらは、文字を扱うプログラムから見ると、それぞれ入力と出力の違いみたいに思っておけば十分だろう。TeXも文字を扱うプログラムなので、これらの両側面に関するエンコーディングがあるのだが、特に日本語だけでTeXを使うぶんには入力エンコーディング以外は意識しなくても済んでしまうし、最近はとにかくUTF-8で済んでしまうので[1]、ほとんど気にしたことがない人が多いような気がしている。

とはいえ、ちょっと違う言語を使いたいとか、好きなフォントを使いたいとか、そういう場合には避けて通れない話である。ところが昔話を知らないと調べてみても馴染みのない用語や概念が多く、しかも整理された解説も見かけない。というわけで本稿では、TeXでこれら入出力のエンコーディングをどう考えればいいかをざっくりと整理することに挑戦してみたい。

なお本稿はTeX & LaTeX Advent Calendar 2024の24日めの記事として書いたものです(23日めは例年通りwtsnさん)。いろいろ余裕がなかったのでコード例や図は一切なく、ひたすら文章がつづきます。書くだけ書いて、いちおうChatGPT o1には「間違いがないか調査して」と投げたけど、読んでもらうための推敲はろくにしていません。がんばって読んでください。勘違いや補足があったらコメントもらえるとうれしいです。

入力エンコーディング

入力エンコーディングについては、原稿を書くうえで通常のプログラムと同じように気にしてる人はいると思うが、TeXにおけるそれはちょっと特殊である。とはいえ、オリジナルのTeXでは入力を7ビットのASCIIと想定していて、これはむしろ(当時の)ふつう。7ビットなので最大でも128文字までしか入力には使えなかった。

7ビットASCIIでも英語の文章なら不自由はないのだろう。しかし同じラテン文字でもヨーロッパ諸言語だとASCIIには存在しない文字をいろいろ使う(発音区別符号が付いたやつとか)。さらにロシア語のようにキリル文字を使う言語や、アラビア語の文字など、そもそもASCIIでは書けないアルファベットも世界にはたくさんある。そのためTeX自体はわりと早々に8ビットの文字を入力できるように拡張され、TeX82以降は入力に256種類の文字を使えるようになった。

ここまでの話には、入力ファイル中の8ビットを文字としてどうやって解釈するか、つまり入力エンコーディングの話は実は出てこない。世界には「8ビットで文字を表すためのエンコーディング」の規格が無数にある(代表的なのはIS0 8859と呼ばれるシリーズ)。8ビット化されたTeXであれば、原理的にそれらすべてに対応できるはずだろう。つまり、各国のOSで標準的に採用されているエンコーディングで保存されたファイルをTeXで読み込み、それを文字として組版できそうに思える。

しかし、TeXのようなプログラムが「入力エンコーディングに対応する」っていうのは、いったい何がどうなることを指すのだろうか? そもそもTeXからしたら、原稿ファイルがどの入力エンコーディングで保存されているかを識別する手段がない。仮にそれらを識別できたとして、異なる入力エンコーディングには基本的に互換性がないのだから、それらを統一的に表現するための形式も必要になる。

もともとTeXは、原稿のファイルがどのエンコーディングで保存されているかに関係なく読み込んだ8ビットを「charcode」という数値に変換し、内部の処理ではそれを使う仕組みだ。つまりTeXの内部における文字はあくまでもcharcodeであり、組版に使うフォントもそれに合わせて用意されている(フォントに関しては後述する)。言い換えると、TeXでは「唯一の入力エンコーディング」とでも言うべきものが暗黙に想定されていて、それに従って入力ファイル中の各8ビットにcharcodeが割り当てられ、それが「TeXのフォント」を使って組版される。このcharcodeの幅が7ビットから8ビットになったのでTeXに入力できる文字が128文字から256文字になりました、というのがここまでの話である。

さまざまな言語で使われている多様な入力エンコーディング規格への汎用の対応は、どうやらTeX自体が備えている枠組みだけでは厳しかったようで、LaTeXにおいてinputencというパッケージが登場してはじめて可能になった。inputencパッケージを使えば、オプションで原稿の執筆に使った入力エンコーディングを明示するだけで、その入力ファイルのすべての8ビットが「LaTeXにおける文字の内部表現」に変換されて読み込まれるようになる。この(TeXとは関係ない)LaTeX独自の文字の内部表現は「LICR」と呼ばれており、要は(ASCIIを除く)すべての文字をTeXのマクロで表現しているだけなのだが、その表現を一意の対応先としたことによって、TeXのcharcodeでは表現できない多様な文字を含む入力エンコーディングにも対応できるようになったわけである。

inputencは、その後は8ビット文字の入力エンコーディングだけでなく、多バイト文字の入力エンコーディングであるUTF-8への対応にも成功した。これは力技で、入力ファイル中の8ビットがASCIIでなかったら「後続のオクテット列を必要なだけ引数として取り自身を含むUTF-8の文字とみなす」という挙動のマクロとして扱うことにより実現している。現在のLaTeXはデフォルトで\usepackage[utf8]{inputenc}を読み込むので、特に何も指定しなくてもUTF-8で保存された入力ファイルを読み込んで処理できてしまう。

このように現在のLaTeXではUnicodeの文字で書いたUTF-8のファイルを正しく読み込めることもあって[2]、実を言うと多言語を扱うためにLuaLaTeXやXeLaTeXといったUnicodeエンジン上のLaTeXが必須というわけではない。これは日本語や中国語も例外ではない。ないのだが、それは完全に入力エンコーディングだけの話であり、組版に使うフォントのエンコーディングという別の課題が残る。

フォントエンコーディング

ふつうのプログラムでは、OSやウィンドウマネージャのようなシステムが水面下でフォントを処理してくれるので、フォントエンコーディングについて気にする必要がない。ところが組版のためのプログラムであるTeXでは、フォントエンコーディングについても利用者が気にしないといけない場面がけっこうある。

まず前提として、前述したように、もともとTeXでは「文字を表すcharcode」と「フォント内のグリフの位置」とが事実上一対一に対応している[3]。このTeXにおけるフォント内のグリフの位置、つまりフォントエンコーディングは、7ビットTeXの時代から使われているものがOT1、拡張された8ビットTeXのために改めて開発されたものがT1と呼ばれている。T1エンコーディングは、先頭の128文字はOT1と同じで、後半の128文字のスロットに「ヨーロッパ諸言語でよく使う文字」を割り当てたエンコーディングになっている。

つまり現代の8ビットTeXでは、T1エンコーディングの範囲にある256文字については、それらのグリフをT1の仕様に従ってアクセスできるようにしたフォントを用意すれば使える[4]。Computer Modernのように古いTeXのフォントも、現代ではLatin ModernとしてT1エンコーディングで使うことが多い。いわゆる「TeXの欧文フォント」はだいたいT1エンコーディングである。

T1エンコーディングにない文字を8ビットTeXで使うには、LaTeX内部で使う文字表現と使用したいフォントを用意し、そのためのフォントエンコーディングを開発すればよい。LaTeXではNFSSというフォント選択のための機構が構築されており、そのインターフェイスを使って定義したフォントエンコーディングをfontencというパッケージを通じて読み込めるようになっている。各言語向けのフォントやフォントエンコーディングがいろいろ開発されているので、たとえばキリル文字はT2Aというフォントエンコーディングをfontencで読み込むだけで出力できるし(もちろんキリル文字のグリフを持つT2Aエンコーディングのフォントが必要)、T5エンコーディングを使えばベトナム語の文字が出力できる。

言語によっては必要な組版機能と合わせて専用のフォントエンコーディングが提供されていることもある。たとえばアラビア文字やヘブライ文字はArabTeXというパッケージを使って組版できる。

念のために付言すると、これらはすべてUnicodeエンジンを必要とせず、ふつうにどんなLaTeXでも使える仕組みである[5]。TeX内部で文字を表す数値と、フォント中のグリフとの対応付けが適切にできていて、原稿で使っているのが8ビット文字だけなら、従来のTeXの枠組みで原理的には組版が可能なのである(実際には後述するように多バイト文字である日本語や中国語の組版でさえ可能)。

で、ここまでは、いわば「TeXで使うことを前提に作られているフォント」におけるフォントエンコーディングの話だった。雑に要約すると、そういうフォントにおいては「TeXやLaTeXが内部で使っている文字にどのグリフを対応させるか」という対応表がすなわちフォントエンコーディングである、と言える。

このようなフォントエンコーディングの考え方、つまり「アプリケーションやシステムで文字を表すのに使っている数値にフォント中の個々のグリフを対応付ける」というやり方は、なにもTeXに限られた話ではない。多くシステムやアプリケーションにおいて、下記のように多様なフォントエンコーディングが用途に応じて開発され、それぞれの世界で利用されている。

  • 入力エンコーディングからグリフを直接対応付ける方式。ASCIIやIS0 8859、あるいはShift_JISやEUCやBig5やGBKなんかの文字コードにグリフを直接対応させている。
  • OSが言語ごとに開発した独自方式。Windows-1252とか1251、MacRomanとかMacCyrillicとかいう名前で知られるエンコーディング。TrueTypeフォントの場合は、ファイル中にcmapとして対応が示されているので、描画アプリケーションではそれを参照する。
  • PostScriptフォントの方式。StandardEncodingとかISOLatin1Encodingとかいう名前のエンコーディング。フォントファイルやアプリケーションで生成したファイル(要するにPDFとか)に、文字コードに対応するグリフの名前の一覧表が埋め込まれている。描画アプリケーションではそれを参照する。
  • Unicodeコードポイントからグリフの位置を対応付ける方式。TrueTypeやOpenTypeのcmapに対応を埋め込んで使う。
  • CID-keyedと呼ばれる方式。Adobeが規定している個々の文字の識別子(CID)にグリフの位置を対応付けて、その対応付けをOpenTypeフォントやPDFのCFFという構造に埋め込んで使う。

これらさまざまな方式でエンコーディングされたフォント(のグリフ)をTeXで使うことはできないのだろうか?

実は使える。TeXには仮想フォントという「フォントエンコーディングを利用者が拡張できる」ような仕掛けがあって、fontencで扱えるTeX向けのフォントエンコーディングを開発しなくても、他の種類のフォントエンコーディングのフォントに含まれているグリフを出力できる。たとえば、後述する(u)pTeXで日本語のOpenTypeフォントを使うときに広く使われているotfパッケージは、Adobe-Japan1という日本語用のCIDでエンコーディングされているOpenTypeフォントに対する仮想フォントである。

日本語TeXのエンコーディング

8ビットTeXには、扱えるアルファベット(文字)の数に256文字という制限がある。同じ制限は、TeXがフォントを扱う仕組みにもある。しかしキリル文字やベトナム文字やアラビア文字はそんなにアルファベットの数がないので、ここまでの枠組みだけで出力にも対処できる。

ところが世界には、日本語や中国語や韓国語のように「256文字ではぜんぜん足りない」ような言語もある。こうした言語をTeXで扱うには、前述したinputencのような仕組みで入力を仮に扱えたとしても、出力のエンコーディングが引き続き課題になる。

日本語圏ではこの課題を「2バイト文字を扱えるTeXエンジンを独自に開発する」というアプローチで解決した。現在ではそのうちpTeXと[6]、それをさらに拡張して内部の文字をUnicodeにしたupTeXが広く利用されている[7]。pTeXもupTeXも、現在のデフォルトの入力エンコーディングはUTF-8である[8]。フォントエンコーディングは、いずれも縦書きと横書きが別々にあって、それぞれpTeXではJT1JY1、upTeXではJT2JY2と規定されている[9](利用者が明示する必要はない)。

なお、文字数が多すぎて8ビットTeXでは扱えないと思われていた日中韓の文書だが、けっこう前からCJKパッケージという形で8ビットTeXでもフォントを扱えるようになっている[10]。したがって、これらの言語をTeXで扱うためにpTeXやupTeX、あるいはUnicodeエンジンが必須というわけでもない。

まとめのようなもの

本稿を通じて理解されるように、多言語の文字をTeXで扱うのに必ずしもUnicodeエンジンが要求されるわけでもない。ふつうのTeXエンジンでも8ビット文字の言語ならかなり多くの入力に対応できるし、最近のLaTeXであればUTF-8もいける。フォントのエンコーディングを適切に対応させればかなり多くの文字も生成できる。

一方、歴史的にはTeXにおける入出力では8ビット文字しか扱えず、そのためpTeXやupTeXが必要だった。これらの日本語用のTeXエンジンには、入出力のエンコーディングに必要な機能だけでなく、日本語の組版に特化した機能(単語間でなく文字間で適切な改行を可能にするなど)も多数組み込まれていて、日本語によるLaTeXの利用普及を大きく促した。

なお、本稿では入出力のエンコーディングに焦点を当てたが、組版にはフォントのシェイプを選択する仕組みや、ハイフネーションやリガチャのための仕組みなども必要になる。そのような処理は、言語ごとのパッケージ(ArabTeXCJK)や、LaTeXのフォント選択機構であるNFSS、あるいはbabelのような多言語パッケージを通して共通化されていることが多い。また、本稿では「通常の文字」のエンコーディングだけを論じたが、記号用のフォントや数式用のフォントでは事情が少しずつ違ってくる(とはいえ基本的なノリは同じである)。

もう1つ注意点があって、本稿ではあたかもTeXがフォントエンコーディングを参照してフォントからグリフを取ってくるプログラムであるかのように書いているが、これは嘘である。TeXエンジンにとってのフォントエンコーディングは、字形のありかではなく、その文字を組版でどう扱うかべきかという情報のありかである。字形のありかは、TeXが生成した組版結果を出力するほうのプログラム(いわゆるDVIウェア)だけが関知するので、DVIウェアに対してもTeXと同じフォントエンコーディングを適切に使うための設定が必要になる。が、DVIウェアによっていろいろ事情が違って面倒なので、TeXのフォントエンコーディングについてうっすら理解するだけなら仮想フォント以外は透過的に扱っていいかなと判断した。

最後になるが、いくらふつうのTeXでけっこういろいろな文字が扱えるとはいっても、これから新しい文書を作る際にはUnicodeエンジンの利用を検討したほうがいいだろう。特にLuaTeXについては、pTeXと同等の日本語組版のための機能もすでに実装されている。

参考文献

エンコーディングについては、『TeXbook』を読んでも「キーボードで入力した文字は、お使いのシステムに合わせてTeXに渡されて、そこではcharcodeとして扱われ、付録Fにある対応表に従ってグリフが出力される」という説明しかないので、現代の事情はよくわからない。そもそもLaTeXにおける文字の内部表現は、NFSSという観点ではあくまでもLICRなので、フォントエンコーディングをcharcodeからの対応表として考えるわけにもいかない。このあたりの話は『The LaTeX Companion』を読むと何となく見えてくる。

fontencに指定できるフォントエンコーディングについては、LaTeXチームによる「LaTeX font encoding」というドキュメントで主要なものがすべて紹介されている。

仮想フォントによるフォントエンコーディングの拡張については、『LaTeX2ε[マクロ&クラス]プログラミング基礎解説』(いわゆる黄色本)が日本語でとにかく詳しい。入手できる機会があるならしておこう。ただしUnicodeやOpenTypeフォントが当たり前の存在になるよりも前の本なので、そうした古い知識については別の手段で補って読む必要がある(本稿もそういう知識を調べる際の補助になればいいなと思って書いてる)。

CJKパッケージにおけるフォントエンコーディングの拡張については「The CJK package for LaTeX2ε — Multilingual support beyond babel」というTUGの記事が参考になる。

otfパッケージについては公式サイトである「Open Type Font用VF」を参照。(u)pTeXの入力エンコーディングについては、本文では雑に「UTF-8がデフォルト」と書いたが、実際にはもう少し賢い方法で決定している。その詳細については「pTeXマニュアル」を参照してほしい。

脚注
  1. 本稿の観点では面白味がないのでほとんど触れないが、現在はUnicodeの文字を直接扱えるTeXエンジン(いわゆるUnicodeエンジン)があり、ここで触れているようなTeXにおけるエンコーディングの諸事情を意識しなくても済むようになりつつある。ここにあるような話がめんどくさい場合は素直にLuaTeXやXeTeXを使いましょう。 ↩︎

  2. Unicodeの結合文字は使えなかったりはするはずなので万能ではない。 ↩︎

  3. 厳密に言うとこれは本文中の話であり、数式中では事情が異なる。本稿では本文におけるフォントエンコーディングの話に限定する。 ↩︎

  4. ただしpdfTeXのデフォルトはOT1なので、後述するfontencT1を明示的にオンにする必要がある。 ↩︎

  5. ちなみにUnicodeエンジンでは、これらのフォントエンコーディングを使うこともできるが、ふつうはTUというUnicode文字のためのエンコーディングが暗黙に使われていて、それだけで事足りる。 ↩︎

  6. これらのエンジンでは、使える文字を日本語に拡張しただけでなく、日本語組版のために必要な仕組みも埋め込まれているのだが、ここではエンコーディングの観点にのみ注目している。 ↩︎

  7. 内部がUnicode化されたということはupTeXはUnicodeエンジンなのではないだろうか、と思うかもしれないが、upTeXではASCII以外のUnicode文字はpTeXが和文を扱うように扱ってしまうので、Unicodeエンジンに分類されることはない。日本語以外の範囲を8ビット文字として扱う(したがってinputencでUTF-8として読ませる)ことで8ビットTeXと同じ能力に戻すことは可能。詳しくはZR氏による解説を参照。 ↩︎

  8. 7ビットTeXがASCIIしか使えなかったように、pTeXの内部ではプラットフォームに応じてShift_JISもしくはEUC-JPがデフォルトで使われる。 ↩︎

  9. 上で「Unicodeエンジンを使うときのフォントエンコーディングはTUで十分」という話をしたが、LuaLaTeXを日本語で使うときにはLuaTeX-jaというパッケージを使い、これは暗黙に横書きはJY3、縦書きはJT3というフォントエンコーディングを使う。 ↩︎

  10. Cで始まる複数のフォントエンコーディングをパッケージで連結することにより256文字を超えるグリフのためのスロットを仮想的に生み出している。 ↩︎

Discussion