マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

xunicode で Unicode しない場合のエラーの話

アレの話。

要するに、xunicode パッケージ(fontspec の中で読み込まれる)を読み込んだ後、(何か不幸な理由があって)従来の 8 ビットのエンコーディングのフォントに切り替えると、分割不可空白の命令〈~〉がエラーになるということである。これは XeLaTeX でも LuaLaTeX でも同様の現象を示す。

\documentclass[a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage{fontspec}
\begin{document}
\usefont{T1}{ptm}{m}{n}Mr.~X
\end{document}

~〉は \nobreakspace という命令に展開されるが、これがエラーになってしまう。

! LaTeX Error: Command \nobreakspace unavailable in encoding T1.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...

l.5 \usefont{T1}{ptm}{m}{n}Mr.~
                               X

エンコーディング依存命令

LaTeX の命令の一部は、エンコーディングに依存して(内部で*1)異なる動作をする。例えば、文字〈æ〉は、OT1 では符号位置 0x1A、T1 では 0xE6 にある。そこで、〈æ〉を出力する命令 \ae は次のように定義されている。((なお、エンコーディング依存命令の定義のための命令(\DeclareText〜)については、LaTeX 公式の「LaTeX2e font selection」(「texdoc fntguide」で読める)に説明がある。))

\DeclareTextSymbol{\ae}{OT1}{"1A}
\DeclareTextSymbol{\ae}{T1}{"E6}

内部動作の話になるが、この時に、「OT1 での \ae の定義」は \OT1\ae という制御綴に収められている。今の場合、これは位置 "1A の chardef に等置されている。((つまり \expandafter\chardef\csname OT1\string\ae\endcsname="1A とされている。))

同様に文字〈ŋ〉を出す命令 \ng は次のように定義される。

\DeclareTextSymbol{\ng}{T1}{"AD}

ここで「OT1 での定義」は与えられていないので、実際に現在のエンコーディングが OT1 の時に \ng を実行するとエラー(「Command \ng unavailable in encoding OT1.」)になる。ここで、

\DeclareTextCommandDefault{\ng}{ng}

とするとフォールバックが定義されて、\ng の定義のないエンコーディング\ng を使うと、「ng」が出力されるようになる。内部的には、これは、\?\ng という制御綴を定義している。

xunicode パッケージの役割

xunicode パッケージは、Unicode エンコーディング(XeLaTeX では「EU1」、LuaLaTeX では「EU2」*2である。ここでは前者で代表する。)に対する「エンコーディング依存命令」の定義を与えている。これにより、例え文字が直接入力できなくても*3従来の LaTeX の命令(\ae\ng 等)での入力が可能になる。例えば、\ng については

\DeclareTextCommand{\ng}{EU1}{\char"014B\relax}

と定義されていて、符号位置 0x014B の文字(つまり〈ŋ〉)が出力される。勿論この際には \EU1\ng という制御綴が定義されている。((この場合、\EU1\ng\char"014B\relax に展開されるマクロになる。なおここで、\DeclareTextCommand{\ng}{T1}{\char"014B\relax} でも同じ目的を実現できる。))

……というのが原理的な話だが、実際には xunicode では

\DeclareUTFcharacter[\UTFencname]{x014B}{\ng}
% \UTFencname は XeLaTeX の場合は EU1

という命令を実行していて、この \DeclareUTFcharacter の処理の中では \EU1\ng というマクロを直接定義している。しかし、\ng が既に「エンコーディング依存命令」として定義されているならばそれで正しく動作する。

\nobreakspace の扱い

さて、\nobreakspace はある意味で U+00A0 NO-BREAK SPACE に対応すると考えられるので、xunicode では

\DeclareUTFcharacter[\UTFencname]{x00A0}{\nobreakspace}

を実行している。これは本来なら

\DeclareTextCommand{\nobreakspace}{EU1}{\char"00A0\relax}

を実行したいところである。ところがここで問題があり、\nobreakspace の既存の定義はエンコーディング依存命令として定義されているのでなく、「\leavevmode\nobreak\␣」に展開される LaTeX-protected な((つまり、\DecalreRobustCommand で定義された、ということ。この場合、\nobreakspace 自体の定義は \protect\nobreakspace␣ となり、実体の定義は \nobreakspace␣ にある。このことは今回の話とは関係がないので、この記事では仮に、\nobreakspace の定義が \leavevmode\nobreak\␣ であると見做すことにする。))命令となっている。従って、単純に「EU1 の定義を追加」という処理ができない。この場合は、\DeclareUTFcharacter は(\EU1\nobreakspace を定義した後で)次のようなコードを実行している。(xunicode.sty v0.981;263行目)

      % ... but when it isn't robust, make it so
      \expandafter\let\csname?-\string#8\endcsname#8\relax
      \edef\next@UTF@{{\cf@encoding}%
        {\expandafter\noexpand\csname?-\string#8\endcsname}}%
      \expandafter\DeclareTextCommand\expandafter
         {\expandafter#8\expandafter}\next@UTF@

ここで #8\nobreakspace である。解り易くいうと次のようになる。

  • \nobreakspace の元の定義 \leavevmode\nobreak\␣ *4\?-\nobreakspace に代入する。
  • \DeclareTextCommand{\nobreakspace}{EU1}{\?-\nobreakspace} を実行する。

この結果、\nobreakspace は次のようなエンコーディング依存命令になる。

  • EU1 での定義は \leavevmode\nobreak\␣
  • それ以外では未定義でエラー。

冒頭で述べた現象の原因は、この \nobreakspace の再定義にある。

「正しい定義」を考える

しかし、xunicode の目的を考えると、正しい動作は次のようでなければならないはずである。

  • EU1 での定義は \char"00A0\relax
  • それ以外では \leavevmode\nobreak\␣

この「それ以外では」の部分は、先に述べたフォールバックの機構を利用すれば実現できる。そう考えると、どうも先の動作に登場する \?-\nobreakspace\?\nobreakspace の誤り(〈-〉が余分)ではないかと推測される。また EU1 の定義(\EU1\nobreakspace)が \char"00A0\relax にならないのは、本当は直前にこの定義を行っているのに、\DeclareTextCommand 実行時にそれが上書きされてしまうからである。((fontspec から xunicode を読むと、\cf@encoding は EU1 になるのだが、これが想定されていない?))

以上のことから、次のようなコードが正しいのではないかと考えている。

      % ... but when it isn't robust, make it so
      \expandafter\let\csname?\string#8\endcsname#8\relax
      \edef\next@UTF@{{OT1}%
        {\expandafter\noexpand\csname?\string#8\endcsname}}%
      \expandafter\DeclareTextCommand\expandafter
         {\expandafter#8\expandafter}\next@UTF@

この中の「OT1」は「EU1 以外で確実に定義済であるエンコーディング」として用いられている。次のような文書でテストしてみる。

\documentclass[a4paper]{article}
\usepackage{fontspec}
\showboxdepth=100 \showboxbreadth=100 % for \showbox
\begin{document}
  % \showbox で出力内容を調べる
\setbox0\hbox{A~B\usefont{T1}{ptm}{m}{n}C~D}
\showbox0
  % 実際にT1で出力させる
\usefont{T1}{ptm}{m}{n}Mr.~X
\end{document}

「Mr. X」と書かれたが PDF 文書が出力され、ログに次のような \showbox の結果が記録される。どうやら意図通りに動いているようである。

> \box0=
\hbox(7.16+0.09991)x35.9699
.\EU1/lmr/m/n/10 A?B             ←〈?〉は実際は U+0080
.\T1/ptm/m/n/10 C
.\penalty 10000                  ←↓既存の\nobreakspace
.\glue 2.5 plus 1.49998 minus 0.59998
.\T1/ptm/m/n/10 D

*1:つまり、同一に見える結果を得るために。

*2:fontspec の機能を用いて定義されるフォントは全てこのエンコーディングを持っている。

*3:そもそも Unicode 対応の XeTeX/LuaTeX では〈ŋ〉を入力すると〈ŋ〉が出力されるわけで、これには何のマクロ定義も要らない。

*4:本当はこれではない。前の注釈を参照。