読者です 読者をやめる 読者になる 読者になる

TeX Alchemist Online

TeX を使って化学のお仕事をしています。

TeX Live 2015 の \extrafloats の問題点とその回避法

TeX

TeX Live 2015 では,LaTeX カーネルに大きな変更が色々ありました。

\stepcounter 時の孫カウンタ以下も含めた再帰的な下位カウンタリセットなどは,一般ユーザにも恩恵が大きい変化です。

日本語の記事では,ZRさんによって色々な解説記事が執筆されています。

この中の「LaTeX が新しくなった話(補足2)」に,「新しい LaTeX カーネルと etex パッケージが非互換」という話があります。これと関連して,次のような問題が存在することに気づきました。

現象

LaTeX でのレジスタ割当の話(1)」に紹介されているように,TeX Live 2015 の新 LaTeX カーネルには,

\extrafloats{<整数>} [命令]: 未処理の浮動体の個数の許容上限を指定の数だけ増加させる。

という新命令が用意されています。 従来 \reserveinserts を使う対処が必要であった「浮動体の個数不足」問題を,より簡単に解決できるようになります。

しかし,etex パッケージとともに \extrafloats を使おうとすると,次のようにエラーとなります。

\documentclass{article}
\usepackage{etex}
\extrafloats{10}
./test.tex:3: Missing = inserted for \ifnum.
<to be read again> 
\relax 
l.3 \extrafloats{10}

原因

この現象の原因を調査してみたところ,次のように原因が判明しました。

まず,\extrafloats は latex.ltx で定義されています。\extrafloats は内部で \ch@ck という命令を呼び出します。\ch@ck は同じく latex.ltx 内で次のように定義されています。

\gdef\ch@ck#1#2#3{%
  \ifnum\count1#1<#2\else
    \errmessage{No room for a new #3}%
  \fi}

一方,etex.sty では

\def\ch@ck#1#2#3#4%
 {\ifnum\count1#1<#2#4\else\errmessage{No room for a new #3}\fi}

と定義されており,引数の数が異なる別の命令で再定義されています。そのため,etex パッケージを読み込むと,\extrafloats が機能しなくなってしまうのです。

latex.ltx で定義された \ch@ck を内部的に使う命令は,他にも \newinsert\alloc@ がありますが,これらは etex.sty の中で再定義されており,問題ありません。しかし,新命令である \extrafloats は etex.sty の中で latex.ltx の \ch@ck に非依存な形に再定義されないため,破壊されてしまうのです。

etex パッケージの読み込みは必要なのか?

LaTeX が新しくなった話 で説明されているように,新カーネルでは etex パッケージを使わずとも,追加レジスタが使えるようになっています。それならば新カーネル使用時に etex パッケージをわざわざ読み込む必要はないように思えます。

しかし,etex パッケージが提供する機能は追加レジスタだけではありません。e-TeX のロゴを出力する \eTeX をはじめ,様々な追加命令が定義されます。

また,自分で明示的に \usepackage{etex} としなくても,呼び出したパッケージが内部的に \RequirePackage で etex パッケージを呼び出す場合もあります。例えば次のようなソースも同じエラーを引き起こします。

\documentclass{article}
\usepackage{linegoal} %%% 内部的に \RequirePackage で etex.sty を呼び出してしまう
\extrafloats{10} %%% => エラー

対策1:\extrafloats の使用はできるだけ前に!

TeX Live 2015 で浮動体の個数を増やしたい場合,他のパッケージが etex パッケージを呼び出す前に,できるだけ早い段階で \extrafloats を使うように心がける とよいでしょう。そうすればこの問題を回避できます。

しかし,\extrafloats は一般ユーザが使うことを想定した命令であり,それを使うタイミングを気にしなくてはいけないというのは,本来的には望ましい設計ではないでしょう。

なお,「LaTeX が新しくなった話(補足2)」に挙げられた「新しい LaTeX カーネルと etex パッケージの非互換性」の例は,「 etex パッケージを遅く読み込むことで誤動作が引き起こされる例」となっています。これを避けるためには,「 etex パッケージの読み込みはできるだけ早い段階で!」を徹底することが必要です。

一方本件の問題を避けるためには,それとは逆に「 \extrafloats の使用は etex パッケージの読み込みよりも前に!」を徹底する必要があります。

このように,TeX Live 2015 の新カーネルと etex パッケージとの両立には,相当気を遣わなくてはならないことが分かります。

対策2:\extrafloats の定義を書き換える

\extrafloats の定義において,後に etex パッケージで書き換えられる可能性のある \ch@ck という命令を使っていることが本件の原因です。そこで,その名前を書き換えてしまいましょう。

%%% etex パッケージが読み込まれるより前にこれを実行
\makeatletter
\let\latexltx@ch@ck\ch@ck % latex.ltx の \ch@ck を別名で退避

%%% latex.ltx に記載された \extrafloats の定義内の \ch@ck を \latexltx@ch@ck に一括置換
\def\extrafloats#1{%
\ifnum#1>\z@
\count@\numexpr\float@count-1\relax
  \latexltx@ch@ck0\count@\count
  \latexltx@ch@ck1\count@\dimen
  \latexltx@ch@ck2\count@\skip
  \latexltx@ch@ck4\count@\box
\e@alloc@chardef\float@count\count@
\expandafter\e@alloc@chardef
            \csname bx@\the\float@count\endcsname\float@count
\@cons\@freelist{\csname bx@\the\float@count\endcsname}%
\expandafter
\extrafloats\expandafter{\numexpr#1-1\relax}%
\fi}%
\makeatother

ですが,このように latex.ltx 内の定義をベタでコピペするよりは,動的に「一括置換」を行った方がスマートでしょう。

%%% etex パッケージが読み込まれるより前にこれを実行
\usepackage{regexpatch}
\makeatletter
\let\latexltx@ch@ck\ch@ck % latex.ltx の \ch@ck を別名で退避
\xpatchcmd*{\extrafloats}{\ch@ck}{\latexltx@ch@ck}{}{} % 定義を一括置換
\makeatother

regexpatch パッケージは,expl3 の機能を利用することで,命令の定義をパッチングする強力なインターフェースを与えてくれます。\xpatchcmd* は,ある命令の定義テキストに含まれる特定のトークン列の並び全てを別のトークン列に一括置換する命令です。これにより,\extrafloats の定義内に現れる \ch@ck を動的に \latexltx@ch@ck へと一括置換しています。