マクロツイーター

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

pTeX で \showbox すると和文間空白がアレ

TeX で複雑な組版を行うマクロの動作確認をする時に欠かせないのが \showbox であるが、和文が絡む場合は注意すべき点がある。それについての話。

確認:\showbox の使い方

本題に入る前に、まず \showbox の使い方を復習しよう。機能の説明は非常に単純で、\showbox<整数> を実行すると、\box<整数> の中身を表すダンプ文字列がログに出力される、というものである。例えば、次の LaTeX 文書をコンパイルしてみる。

\documentclass[a4paper]{article}
\showboxdepth=100
\showboxbreadth=100
\begin{document}
\setbox0\hbox{To\nobreak\ ?}
\showbox0
\end{document}

すると、画面に次のように表示されて停止する。

! OK (see the transcript file).
l.6 \showbox0

?

エラー発生のときの表示と似ている((プロンプトの種類としては同じ。要するに \show\showthe と同じ。))が、これはエラーではないので Enter を空打ちして先に進む。すると処理が終了し、ログファイル(*.log)中に次のような記述が残される。(# 以降の日本語は説明のために私が追記したもの。)

> \box0=
\hbox(6.94444+0.0)x19.44446                       # (高さ+深さ)x幅 を表す
.\OT1/cmr/m/n/10 T                                # 文字〈T〉: \OT1... はfontdefトークン
.\kern-0.83334                                    # 自動挿入のカーン
.\OT1/cmr/m/n/10 o                                # 文字〈o〉
.\penalty 10000                                   # ペナルティ(←\nobreak)
.\glue 3.33333 plus 1.66666 minus 1.11111         # グルー←(\ )
.\OT1/cmr/m/n/10 ?                                # 文字〈?〉
                                          # なお先頭の「.」は箱のネストレベルを表す
! OK.
l.6 \showbox0
             
?

\showbox の使用についていくつか注意をしておく。

  • 整数パラメタ \showboxdepth はダンプ対象とするネストレベルの最大値を表す。
  • 整数パラメタ \showboxbreadth は 1 つの(ネストした)箱についての表示対象の最大個数を表す。
  • LaTeX の既定ではこの 2 つのパラメタは 0 でありこの状態では \showbox しても何も出ない。適切な値を設定する必要がある。
  • 整数パラメタ \tracingonline の値が 1 以上の場合は(ログだけでなく)画面にもダンプ文字列が出力される。
  • 画面に出力しない場合でも停止する。停止しないようにするには前もって scroll mode に切り替え(\scrollmode を実行)ておけばよいだろう。((scroll mode では \show< でもエラーでも停止しない。\errorstopmode で元のモードに戻る。))

また、(\showbox とは別に)TeX にはページ出力をダンプする機能がある。\tracingoutput の値を 1 以上に設定すると、ページ出力の度にその中身のダンプが \showbox と同様の方式で行われる。((先に説明した \showbox 用のパラメタはページダンプの時にも適用される。))

和文を含む場合の出力

和文の含む場合でも \showbox の使い方自体は変わらない。次のような例で試してみる。

\documentclass[a4paper]{jsarticle}
\showboxdepth=100
\showboxbreadth=100
\begin{document}
\setbox0\hbox{(C言語)}
\showbox0
\end{document}

結果は次のようになる。*1

> \box0=
\hbox(7.4726+1.33438)x37.46283, yoko direction
.\JY1/mc/m/n/10 (                                  # 文字〈(〉
.\penalty 10000(for kinsoku)                        # 禁則ペナルティ
.\OT1/cmr/m/n/10 C                                  # 文字〈C〉
.\glue(\xkanjiskip) 2.5 plus 1.49994 minus 0.59998  # 和欧文間空白(\xkanjiskip)
.\JY1/mc/m/n/10 言                                  # 文字〈言〉
.\JY1/mc/m/n/10 語                                  # 文字〈語〉
.\penalty 10000(for kinsoku)                        # 禁則ペナルティ
.\glue(\kanjiskip) 0.0 plus 0.92473 minus 0.0924    # 和文間空白(\kanjiskip)
.\JY1/mc/m/n/10 )                                  # 文字〈)〉
.\glue(refer from jfm) 0.0                          # 無意味なグルー


! OK.
l.6 \showbox0

?

このダンプ結果を見ると、大体は入るべきものが入っているように見えるが、実は 1 つ足りないものがある。〈言〉と〈語〉の間に和文間空白(\kanjiskip)が入るべきなのに入っていない。一方、〈語〉と〈)〉の間の \kanjiskip は入っている。

\kanjiskip が入らないはずがないので、入っていることを確かめるために、\kanjiskip の自然長をゼロ以外にしてみる。

\documentclass[a4paper]{jsarticle}
\showboxdepth=100
\showboxbreadth=100
\begin{document}
\advance\kanjiskip20pt % 自然長を 20pt に変更した
\setbox0\hbox{(C言語)}
\showbox0
\box0                  % 実際に箱の中身を出力させる
\end{document}
> \box0=
\hbox(7.4726+1.33438)x77.46283, yoko direction
.\JY1/mc/m/n/10 (
.\penalty 10000(for kinsoku)
.\OT1/cmr/m/n/10 C
.\glue(\xkanjiskip) 2.5 plus 1.49994 minus 0.59998
.\JY1/mc/m/n/10 言
.\JY1/mc/m/n/10 語
.\penalty 10000(for kinsoku)
.\glue(\kanjiskip) 20.0 plus 0.92473 minus 0.0924
.\JY1/mc/m/n/10 )
.\glue(refer from jfm) 0.0

ダンプ出力の中の箱の幅の値を見ると、40pt 増えている(37.46283 → 77.46283)。つまり \kanjiskip は箱の中に 2 つある訳であり、出力を見る限り、それが〈言〉と〈語〉の間にあることは明らかである。

出力されない和文間グル―

実は、pTeX における \showbox の出力には次のような「癖」があり、前述の奇妙な結果もそれによるものである。

箱の中で隣接する 2 つの和文文字(のノード*2)の間には必ず \kanjiskip が存在するがそれは \showbox によるダンプには表示されない。

こんな仕様になっている理由は、そもそも pTeX がメモリ使用効率を上げるために、「隣接する 2 つの和文文字ノードの間に必然的に入る \kanjiskip のグルーのノードは(メモリ上の「箱」のデータの中に)置かず、しかし常に存在するものとして扱う」*3という処理を行っているからである。従って、pTeX の内部処理では\kanjiskip の扱いは少し特殊になっている。例えば、〈)〉の前に禁則ペナルティを入れる処理では、論理的には「ペナルティのノードが入るだけ」であるが、そうすると、〈語〉と〈)〉のノードが隣接でなくなり暗黙のルールで「あることになっていた」\kanjiskip が消えてしまうので、それを補うために「\kanjiskip のノード」も一緒に挿入する、という処理をとっている。((なお、〈語〉と〈)〉の間は完全な分割禁止なので、ここには伸縮する和文間空白は入れるべきでない(行調整のため \kanjiskip が大きく伸びてしまう場合でも、この位置には空きが入って欲しくない)という意見もあるだろう。しかし jis メトリックではこの位置の \kanjiskip を入れる規則になっている。(無論調節は可能である。)))

またこの処理方式から考えると、和文文字ノード同士が間に(論理的に)何も挟まずに隣接することはないことが解る。例えば、次のようにして〈、〉の直後のメトリックグルーを消去する。

\hbox{ぐ、\inhibitglue はぁ}

この場合、内部動作としてメトリックグルーの挿入を単純に抑制している。ところが、そうすると和文文字が隣接することになり、\kanjiskip があると見做されるはずである。実際に pTeX の仕様はそのようになっている。参考として、この箱を \showbox した結果は以下の通り。

> \box0=
\hbox(7.4726+1.33438)x32.36404, yoko direction
.\JY1/mc/m/n/10 ぐ
.\penalty 10000(for kinsoku)
.\glue(\kanjiskip) 0.0 plus 0.92473 minus 0.0924
.\JY1/mc/m/n/10 、
.\JY1/mc/m/n/10 は
.\penalty 150(for kinsoku)
.\glue(\kanjiskip) 0.0 plus 0.92473 minus 0.0924
.\JY1/mc/m/n/10 ぁ

〈、〉と〈は〉の間に「実質的に何もない」状態にするには、\inhibitglue に加えて、\hskip0pt 等を入れる必要がある。これまでに述べたことを踏まえると、この一見複雑な仕様が内部実装としては実は単純であることが解るだろう。

*1:前の記事で、「hbox の内側の両端にメトリックグルーが入ることはない」と書いたが、この結果を見るとダミーのグルーがあることが判る。ただしその幅も伸縮もゼロなので実際には無いのと同じである。

*2:ノードとは TeX の内部で文字やグルーやペナルティなどの「箱の中にあるもの」を表すオブジェクトのことである。「隣接する」というのは、間には「ソースにはないが自動挿入されるノード」(禁則ペナルティ等)も存在しないということに注意。

*3:約物意外の和文文字同士の間には必ず和文間空白があるという規則だから、普通の日本語文書においては、和文間空白のノードの個数は和文文字の個数に匹敵することになり、(単純計算すると)メモリの半分が和文間空白で埋まってしまう。