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

TeX Alchemist Online

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

TeX Live 2014 の pTeX 系列における \inhibitglue の仕様変更

TeX

TeX Live 2014 では,\inhibitglue に関する仕様の変更が取り込まれました。
pTeX 系列のエンジン(pTeX/e-pTeX/upTeX/e-upTeX)の全てに影響します。

これは,既存ソースの組版結果が変わる重大な変更ですので,今回の変更に至る過去の経緯,および新仕様の取り扱い上の注意点について述べておこうと思います。

背景 - pTeX のバグ

ASCIIのpTeXの仕様書には,次のように書かれています。

\inhibitglue

和文フォントのメトリック情報から、自動的に挿入されるグルーの挿入を禁止します。 このプリミティブを挿入した箇所にのみ有効です。

このように,\inhibitglue は「挿入した箇所のみに有効」と明記されています。
しかし,(TeX Live 2013 以前では)実際にはそうではなく,「次に初めて和文文字が現れた箇所で有効」という挙動をします。この「\inhibitglue の影響の染み出し」は,非和文文字が続く限り,段落をまたいでどこまでも波及します

実例1

ソース
\documentclass{jsarticle}
\begin{document}
\setlength\parindent{0zw}
\everypar{}

hoge:「ほげ」

hoge\inhibitglue hoge

hoge:「ほげ」
\end{document}
出力結果 (TeX Live 2013 の e-pTeX)

f:id:doraTeX:20140714082505p:plain

このように,2段落目の真ん中で発行した \inhibitglue が,段落をまたいで,3段落目の途中のカギ括弧の部分で発動していることが分かります。

この挙動は大変厄介なものでした。ひとたび \inhibitglue を発行してしまうと,それが意図しない所で効いてしまうからです。

実例2

例えば,「日本語組版処理の要件」の行頭の​始め括弧類の​配置方法の(a)「改行行頭の字下げは全角アキ,折返し行頭は天付き」を実現するために,

\setlength\parindent{1zw}
\everypar{\inhibitglue}

としてしまうと,段落冒頭に和文文字がない場合,意図しないところで \inhibitglue が効いてしまいます。

ソース
\documentclass{jsarticle}
\begin{document}
\setlength\parindent{1zw}
\everypar{\inhibitglue}

\noindent
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

「ほげ」はサンプルプログラムなどで利用される「メタ構文変数」である。「ほげ」が使われた場合,それが「意味のない名前」であることが明らかとなる。
% ↑「改行行頭の字下げは全角アキ,折返し行頭は天付き」のテスト

hoge:「ほげ」

和文hoge:「ほげ」
\end{document}
出力結果 (TeX Live 2013 の e-pTeX)

f:id:doraTeX:20140714084219p:plain

「改行行頭の字下げは全角アキ,折返し行頭は天付き」は確かに実現できていますが,\everypar から挿入された \inhibitglue が染み出すために,2段落目の途中のカギ括弧の所に \inhibitglue が効いてしまっています。一方,3段落目では,既に和文文字が登場していますので,その瞬間に \inhibitglue はリセットされており,カギ括弧部分に \inhibitglue は効きません。

過去の経緯と対策 (TeX Live 2013 以前)

この \inhibitglue 問題は,これまでも何度も話題になってきました。

このバグを回避するために,様々な対策がとられてきました。

jsarticle における \@inhibitglue

先程の「改行行頭の字下げは全角アキ,折返し行頭は天付き」を実現するために,jsarticle.cls では,直接 \everypar{\inhibitglue} とするのではなく,次のような細工がなされています。

\def\@inhibitglue{%
  \futurelet\@let@token\@@inhibitglue}
\def\@@inhibitglue{%
  \ifx\@let@token\inhibitglue
  \else
    \ifx\@let@token\inhibitglue
    \else
      \ifx\@let@token\inhibitglue
      \else
        \ifx\@let@token\inhibitglue
        \fi
      \fi
    \fi
  \fi}
\let\everyparhook=\@inhibitglue
\AtBeginDocument{\everypar{\everyparhook}}

これは,段落冒頭の文字を \futurelet でチェックして,それが開き括弧類である場合に限って \inhibitglue を挿入しようというものです。

強制改行後の処理の変更

TeX Q&A の 53900 のパターンの問題を解決するために,53909 では,強制改行後の処理を「空白トークンを読み飛ばして \@inhibitglue」とするように変更して対処されています。

\inhibitglue が効いていればリセット

このように,不用意に \inhibitglue を発行すると,思わぬ所で効いてしまい危険でした。
発行する側が注意していればよいのですが,「他人の作ったマクロが \inhibitglue を発行している可能性がある」というような状況下では,「\inhibitglue の影響をリセットさせる」という処理が必要になる場面もあります。
そうしたとき,ややトリッキーですが,次のようにすることで,そこまでに効いている \inhibitglue の影響をリセットさせるというテクニックもありました。

{\setbox0\vbox{}}

TeX Live 2014 での修正

TeX Q&A で上記問題が話題になったとき,北川さんが修正パッチを作ってくださいましたが,TeX Live には長らく取り込まれないままでした。
ですが,\inhibitglue 問題が昨年話題になったときにNorbertさんに伝わったためか,TeX Live 2014 では,めでたく北川さんによる\inhibitglue の修正が取り込まれました!

これにより,\inhibitglue が,本来のASCIIの仕様書通りに,「挿入した箇所にのみ有効」となりました。
TeX Live 2013 以前で必要であった上記の様々な工夫は,もはや不要となりました。
例えば \everypar も単に

\everypar{\inhibitglue}

として問題なくなります。(従来の \futurelet を用いたままでも特に問題はありませんが。)

新仕様での注意点

ただし,新仕様での注意点があります。\inhibitglue が垂直モード中で効かないという点です。

実例1

ソース

\everypar が空の状態のとき,手動で \inhibitglue を挿入しようとしたソースです。

\documentclass{jsarticle}
\begin{document}
\setlength\parindent{0zw}
\everypar{}

■■■■■■

「ほげ」%% \everypar が空なので行頭二分アキになる

\inhibitglue 「ほげ」%% 行頭天ツキにしたい

\leavevmode\inhibitglue 「ほげ」%% 行頭天ツキにしたい
\end{document}
出力結果 (TeX Live 2014 の e-pTeX)

f:id:doraTeX:20140714094204p:plain

このように,2つめの

\inhibitglue 「ほげ」

の \inhibitglue は効いていません。\inhibitglue を発行しているタイミングが垂直モードだからです。3つめのように,\leavevmode で水平モードに移行してから \inhibitglue を発行すれば問題ありません。
\everypar{\inhibitglue} とした場合は,水平モードに移行してから \inhibitglue が発行される(トークンリストに \inhibitglue が挿入される)ので \inhibitglue が効きます。

実例2

  • \parbox の中では \everypar が空にリセットされる。
  • \centering の下での強制改行 \\ では \par が発行されて一度(内部)垂直モードに移行する。

ということの影響により,強制改行や \par の直後に開き括弧類がある場合の挙動に注意が必要です。TeX Live 2013 以前と 2014 以降では組版結果が変わってきます。

ソース

幅4zwの \parbox に中央揃えで文字を配置しようとした例です。

\documentclass{jsarticle}
\setlength\fboxsep{0pt}
\newcommand{\centerbox}[1]{\fbox{\parbox{4zw}{\centering#1}}}
\begin{document}
\centerbox{%
ほげ\\
「ほげ」\par
「ほげ」
}

\centerbox{%
ほげ\\
\inhibitglue 「ほげ」\par
\inhibitglue 「ほげ」
}

\centerbox{%
ほげ\\
\leavevmode\inhibitglue 「ほげ」\par
\leavevmode\inhibitglue 「ほげ」
}
\end{document}
出力結果 (TeX Live 2013 の e-pTeX)

f:id:doraTeX:20140714103736p:plain
このように,TeX Live 2013 以前では,2番目のように,\\ や \par の後に \inhibitglue を直接置いておけば,意図通りに中央揃えで配置されます。

出力結果 (TeX Live 2014 の e-pTeX)

f:id:doraTeX:20140714104046p:plain
このように,TeX Live 2014では,2番目のように,\\ や \par の後に \inhibitglue を直接置いても,開き括弧の前にグルーが挿入されてしまい,中央揃えになりません。
3番目のように,\leavevmode を入れておく必要があります。

参考:LuaTeX-ja の場合

LuaTeX-ja の場合,そもそも,段落冒頭の開き括弧類の前の部分には,(デフォルトでは)JFM由来のグルーは挿入されません。したがって,\everypar{\inhibitglue} というような対処も不要となります。

ただし,「垂直モードでの \inhibitglue は効かない」という点は TeX Live 2014 以降のpTeXと同様です。これは,次の例のように \everypar に文字を入れてみると見えてきます。

ソース

\documentclass{ltjsarticle}
\usepackage[ipaex]{luatexja-preset}
\begin{document}
\setlength\parindent{1\zw}

\noindent 
■■■■■■

\everypar{}

「ほげ」

\inhibitglue 「ほげ」

\leavevmode\inhibitglue 「ほげ」

\noindent 「ほげ」

\noindent\inhibitglue 「ほげ」

\noindent 
■■■■■■

\everypar{}

「ほげ」

\inhibitglue 「ほげ」

\leavevmode\inhibitglue 「ほげ」

\noindent 「ほげ」

\noindent\inhibitglue 「ほげ」

\end{document}

出力結果 (TeX Live 2014 の LuaTeX-ja)

f:id:doraTeX:20140714105136p:plain

このように,TeX Live 2014 のpTeXと同様,\leavevmode や \noindent で水平モードに移行しない限り,\inhibitglue は効いていないことが分かります。