pTeX のペナルティを LuaTeX-ja で試してみた
これは TeX & LaTeX Advent Calendar 2019 の 15 日目の記事です。昨日は hak7a3 さんでした。明日は keisuke495500 さんです。
今年は残念ながら TeXConf 2019 が中止になってしまいました。私は「pTeX のペナルティ」という題目で一般講演を申し込んでいたのですが,発表スライドを使う予定がなくなったのでここで公開します。
- 一般講演:山下弘展「pTeX のペナルティ」 -- 発表スライド(PDF 直リンク)
- TeXConf 2019 の講演スライドのソース (GitHub)
…さて,TeX & LaTeX Advent Calendar 2019 の重点テーマは「とにかく Lua(La)TeX しよう」です。pTeX ではありません。というわけで,先のスライドと同じことを LuaTeX (LuaTeX-ja) でやってみるとどうなるか,試してみることにしましょう。
おことわり:本稿はあくまで実験結果で,何らかの結論を導くものではありません。「ただやってみた」という程度に捉えてください。
前提
以下,例のスライドの PDF におけるページ番号に対応しながら見ていきます。まず p.3 の「前提」です。
LuaTeX-ja では,pTeX 特有のプリミティブ (\prebreakpenalty, \postbreakpenalty, \jcharwidowpenalty) がそのままの形では実装されていません。代わりのインタフェースが用意されていますので,上記と同じ効果を得るには以下のように書きます。
\input luatexja.sty % 禁則ペナルティ \ltjsetparameter{prebreakpenalty={`.,10000}} \ltjsetparameter{prebreakpenalty={`。,10000}} \ltjsetparameter{postbreakpenalty={`(,10000}} \ltjsetparameter{postbreakpenalty={`(,10000}} % 一文字だけの行を抑制 \ltjsetparameter{jcharwidowpenalty=500}
LuaTeX-ja における禁則処理とペナルティ
p.4--9 で,和文組版の禁則処理が pTeX でどう実装されているかを説明しています。
LuaTeX-ja は pTeX を真似ているので,基本的な考え方は共通です。ただし,先述のとおりでプリミティブが存在しないので,インタフェースだけが若干違います。
ここで,説明の都合上 p.10--11 はいったん飛ばして p.12 以降へ進みます。p.14 のおまじないは LuaTeX-ja でもそのまま使えますが,簡単のためこんなテンプレートを使います。
\tracingonline1 \showboxdepth10000 \showboxbreadth10000 \input luatexja.sty \ltjsetparameter{jcharwidowpenalty=0}% 今回の検証には邪魔なので消す \jfont\x={file:HGRME.TTC:jfm=ujis} \x % 和文フォントを \x という名前で選択 \font\A=ec-lmr10 \A % 欧文フォントを \A という名前で選択
禁則ペナルティの挿入タイミング
和文文字の場合
さて,pTeX では和文文字の禁則ペナルティはその文字ノードをリストに追加するタイミングと同時に挿入されます。
一方,LuaTeX-ja ではどうでしょうか。実際の例で見てみましょう。
\setbox0=\vbox{です。\showlists}\showbox0
LuaTeX では \showlists の時点では禁則ペナルティらしきものが存在しません。
### horizontal mode entered at line 10 (中略) \hbox(0.0+0.0)x20.0, direction TLT \A で \A す \A 。 (中略) ! OK. l.10 \setbox0=\vbox{です。\showlists }\showbox0 ?
一方,\showbox0 の時点では禁則ペナルティが現れます(赤字の部分)。
> \box0=
\vbox(8.8+1.2)x469.75499, direction TLT
(中略)
..\hbox(0.0+0.0)x20.0, direction TLT
..\norule(8.8+1.2)x0.0
..\x で
..\glue 0.0 plus 0.4 minus 0.4
..\norule(8.8+1.2)x0.0
..\x す
..\penalty 10000
..\glue 0.0 minus 0.4
..\glue 0.0
..\hbox(8.8+1.2)x5.0, direction TLT
...\x 。
(中略)
! OK.
l.10 \setbox0=\vbox{です。\showlists}\showbox0
?
このことから,LuaTeX-ja では和文文字の禁則ペナルティは文字ノードと同時ではなく後から挿入されることがわかります。ということは,p.17 に示した以下の pTeX の定理は,LuaTeX-ja では成り立たないことになります。
代わりに,LuaTeX-ja では以下の定理が成り立ちます:
- 和文文字の \prebreakpenalty や \postbreakpenalty は,リストの構築中には挿入されず,ボックスを組み終わった時点で初めて挿入される。
- したがって「現在のリストの最後」にはなりえず,その値を \lastpenalty で取得したり \unpenalty で取り除いたりすることはできない。
それでは p.18 を真似たコードで試してみましょう。1つ目の例:
\setbox0=\vbox{まず(\showthe\lastpenalty は}\showbox0
> 0.
l.14 \setbox0=\vbox{まず(\showthe\lastpenalty
は}\showbox0
?
> \box0=
(中略)
..\x ま
..\glue 0.0 plus 0.4 minus 0.4
..\norule(8.8+1.2)x0.0
..\x ず
..\glue 0.0 plus 0.4
..\glue 5.0 minus 5.0
..\hbox(8.8+1.2)x5.0, direction TLT
...\x (
..\penalty 10000
..\glue 0.0 minus 0.4
..\glue 0.0
..\norule(8.8+1.2)x0.0
..\x は
(中略)
! OK.
l.14 ...{まず(\showthe\lastpenalty は}\showbox0
?
この例では \showthe\lastpenalty で 0 が返っていますが,\showbox すると "(" と "は" の間に 10000 の禁則ペナルティが挿入されています。次に2つ目の例:
\setbox0=\vbox{まず(\unpenalty は}\showbox0
> \box0=
(中略)
..\x ま
..\glue 0.0 plus 0.4 minus 0.4
..\norule(8.8+1.2)x0.0
..\x ず
..\glue 0.0 plus 0.4
..\glue 5.0 minus 5.0
..\hbox(8.8+1.2)x5.0, direction TLT
...\x (
..\penalty 10000
..\glue 0.0 minus 0.4
..\glue 0.0
..\norule(8.8+1.2)x0.0
..\x は
(中略)
! OK.
l.16 ...box0=\vbox{まず(\unpenalty は}\showbox0
?
このように,\unpenalty では禁則ペナルティ 10000 を取り除けていません。
欧文文字の場合
p.19--26 では,pTeX の欧文文字の禁則ペナルティ挿入が和文文字に比べて「少し遅い」という議論があります。しかし,LuaTeX-ja ではそもそも和文文字も「遅い」ので,結果的に差はなくなります。したがって,上の定理をそのまま欧文文字にも拡張できます。
- LuaTeX-ja では,和文文字・欧文文字によらず,\prebreakpenalty や \postbreakpenalty はリストの構築中には挿入されず,ボックスを組み終わった時点で初めて挿入される。
- したがって「現在のリストの最後」にはなりえず,その値を \lastpenalty で取得したり \unpenalty で取り除いたりすることはできない。
pTeX 系列で使っていたコードを LuaTeX-ja に移植するときには,この微妙な差が影響する可能性もありますので,頭に入れておくとよい…というのは言い過ぎでしょうか。とりあえずこんな違いがある,という程度に捉えて忘れてください。
禁則処理の制約事項
飛ばしたページに戻ります。p.10--11 で,pTeX の禁則処理には制約事項があることを述べています。LuaTeX-ja ではどうなのでしょうか。順に見ていきましょう。
スライドには載せませんでしたが,upTeX でこれを確認するには以下のコードを実行すればよいです。(文字「¡」は U+00A1 です。)
\tracingonline1 \showboxdepth10000 \showboxbreadth10000 \jfont\x=upjisr-h \x \font\A=ec-lmr10 \A \postbreakpenalty`¡=7000 % => これは \postbreakpenalty"A1=7000 と同値 \setbox0=\vbox{あ¡\relax あ\showlists} \setbox2=\vbox{あ\kchar"A1\relax あ\showlists} \setbox4=\vbox{あ\char"A1\relax あ\showlists} \box0 % => 和文文字 ¡ を出力 \box2 % => 和文文字 ¡ を出力 \box4 % => 欧文文字 ą を出力 \bye
結論としては,LuaTeX-ja でもこれは同じです。LuaTeX-ja には upTeX の \char(欧文用)と \kchar(和文用)に似た \ltjalchar, \ltjjachar という命令が追加されているので,これも使ってみました。
%#!luatex \tracingonline1 \showboxdepth10000 \showboxbreadth10000 \input luatexja.sty \ltjsetparameter{jcharwidowpenalty=0}% 今回の検証には邪魔なので消す \jfont\x={file:HGRME.TTC:jfm=ujis} \x \font\A=ec-lmr10 \A \ltjsetparameter{postbreakpenalty={`¡,7000}}% => \ltjsetparameter{postbreakpenalty={"A1,7000}} と同値 \setbox0=\vbox{あ¡\relax あ}\showbox0 \setbox2=\vbox{あ\ltjjachar"A1\relax あ}\showbox2 \setbox4=\vbox{あ\ltjalchar"A1\relax あ}\showbox4 \setbox6=\vbox{あ\char"A1\relax あ}\showbox6 \box0 % => 欧文文字 ą を出力 \box2 % => 和文文字 ¡ を出力 \box4 % => 欧文文字 ą を出力 \box6 % => 欧文文字 ą を出力 \bye
グループ境界での禁則ペナルティ
p.27--32 に進みましょう。現行の pTeX にはアレ🍣な挙動が存在します。
LuaTeX-ja ではどうでしょうか。
例1:
\ltjsetparameter{yalbaselineshift=0pt} \setbox0=\vbox{(漢({漢}{(}漢}\showbox0
> \box0= \vbox(8.8+2.5)x469.75499, direction TLT (中略) ..\hbox(0.0+0.0)x20.0, direction TLT ..\A ( ..\penalty 10000 ..\glue 0.0 ..\norule(8.8+1.2)x0.0 ..\x 漢 ..\glue 2.40553 plus 1.0 minus 1.0 ..\A ( ..\penalty 10000 ..\glue 0.0 ..\norule(8.8+1.2)x0.0 ..\x 漢 ..\glue 2.40553 plus 1.0 minus 1.0 ..\A ( ..\penalty 10000 ..\glue 0.0 ..\norule(8.8+1.2)x0.0 ..\x 漢 (中略) ! OK. l.11 \setbox0=\vbox{(漢({漢}{(}漢}\showbox0
例2:
\ltjsetparameter{yalbaselineshift=0pt} \setbox2=\vbox{漢.{漢}. 漢{.}}\showbox2
> \box2= \vbox(8.8+1.2)x469.75499, direction TLT (中略) ..\x 漢 ..\penalty 10000 ..\glue 0.0 ..\A . ..\glue 2.40553 plus 1.0 minus 1.0 ..\norule(8.8+1.2)x0.0 ..\x 漢 ..\penalty 10000 ..\glue 0.0 ..\A . ..\glue(\spaceskip) 4.44444 plus 4.99997 minus 0.37036 ..\norule(8.8+1.2)x0.0 ..\x 漢 ..\penalty 10000 ..\glue 0.0 ..\A . (中略) ! OK. l.12 \setbox2=\vbox{漢.{漢}. 漢{.}}\showbox2
例3:
\ltjsetparameter{yalbaselineshift=3pt} \setbox4=\vbox{(漢({漢}{(}漢}\showbox4
> \box4= \vbox(8.8+5.5)x469.75499, direction TLT (中略) ..\A ( ..\penalty 10000 ..\glue 0.0 ..\norule(8.8+1.2)x0.0 ..\x 漢 ..\glue 2.40553 plus 1.0 minus 1.0 ..\norule(0.0+5.5)x0.0 ..\A ( ..\penalty 10000 ..\glue 0.0 ..\norule(8.8+1.2)x0.0 ..\x 漢 ..\glue 2.40553 plus 1.0 minus 1.0 ..\norule(0.0+5.5)x0.0 ..\A ( ..\penalty 10000 ..\glue 0.0 ..\norule(8.8+1.2)x0.0 ..\x 漢 (中略) ! OK. l.14 \setbox4=\vbox{(漢({漢}{(}漢}\showbox4
例4:
\ltjsetparameter{yalbaselineshift=3pt} \setbox6=\vbox{漢.{漢}. 漢{.}}\showbox6
> \box6= \vbox(8.8+3.0)x469.75499, direction TLT (中略) ..\x 漢 ..\penalty 10000 ..\glue 0.0 ..\norule(0.0+3.0)x0.0 ..\A . ..\glue 2.40553 plus 1.0 minus 1.0 ..\norule(8.8+1.2)x0.0 ..\x 漢 ..\penalty 10000 ..\glue 0.0 ..\norule(0.0+3.0)x0.0 ..\A . ..\glue(\spaceskip) 4.44444 plus 4.99997 minus 0.37036 ..\norule(8.8+1.2)x0.0 ..\x 漢 ..\penalty 10000 ..\glue 0.0 ..\norule(0.0+3.0)x0.0 ..\A . (中略) ! OK. l.15 \setbox6=\vbox{漢.{漢}. 漢{.}}\showbox6
おお,すべて正常です! グループ境界でペナルティが入ったり入らなかったりするという pTeX の気まぐれな挙動が,LuaTeX-ja では改善されていることがわかりました。
リガチャと禁則ペナルティ
p.33--39 に書かれているややこしい話です。
改めて upTeX で試してみます。
\prebreakpenalty`\^^T=2560 \prebreakpenalty`\>=128 \setbox0=\hbox{あ>> い\char`\^^T}\showbox0 \postbreakpenalty`\^^S=256 \postbreakpenalty`\<=1280 \setbox2=\hbox{<<あ \char`\^^Sい}\showbox2
> \box0= \hbox(8.79999+1.2)x34.44432, yoko direction .\displace 0.0 .\x あ .\penalty 128(for kinsoku) .\A ^^T (ligature >>) .\glue 3.33333 plus 1.66666 minus 1.11111 .\x い .\penalty 2560(for kinsoku)
.\A ^^T ! OK. l.10 \setbox0=\hbox{あ>> い\char`\^^T}\showbox0 ? > \box2= \hbox(8.79999+1.2)x34.44432, yoko direction .\displace 0.0 .\A ^^S (ligature <<) .\penalty 256(for kinsoku) .\x あ .\glue 3.33333 plus 1.66666 minus 1.11111 .\A ^^S .\penalty 256(for kinsoku) .\x い ! OK. l.13 \setbox2=\hbox{<<あ \char`\^^Sい}\showbox2 ?
このように,pTeX 系列では
という挙動を示します。一方,LuaTeX-ja で試してみると…。
\ltjsetparameter{prebreakpenalty={`\^^T,2560}} \ltjsetparameter{prebreakpenalty={`\>,128}} \setbox0=\hbox{あ>> い\char`\^^T}\showbox0 \ltjsetparameter{postbreakpenalty={`\^^S,256}} \ltjsetparameter{postbreakpenalty={`\<,1280}} \setbox2=\hbox{<<あ \char`\^^Sい}\showbox2
> \box0= \hbox(8.8+1.2)x34.44432, direction TLT .\whatsit4=[] .\norule(8.8+1.2)x0.0 .\x あ .\penalty 128 .\glue 0.0 .\A ^^T (ligature >>) .\glue(\spaceskip) 3.33333 plus 1.66666 minus 1.11111 .\norule(8.8+1.2)x0.0 .\x い .\penalty 2560 .\glue 0.0 .\A ^^T ! OK. l.12 \setbox0=\hbox{あ>> い\char`\^^T}\showbox0 ? > \box2= \hbox(8.8+1.2)x34.44432, direction TLT .\whatsit4=[] .\A ^^S (ligature <<) .\penalty 1280 .\glue 0.0 .\norule(8.8+1.2)x0.0 .\x あ .\glue(\spaceskip) 3.33333 plus 1.66666 minus 1.11111 .\A ^^S .\penalty 256 .\glue 0.0 .\norule(8.8+1.2)x0.0 .\x い ! OK. l.15 \setbox2=\hbox{<<あ \char`\^^Sい}\showbox2 ?
LuaTeX-ja では prebreakpenalty だけでなく postbreakpenalty もリガチャの構成要素から決まるという挙動になっています。
- \postbreakpenalty はリガチャ自体ではなく,それを構成する最後の要素文字コードから決まる
- \prebreakpenalty はリガチャ自体ではなく,それを構成する最初の要素文字コードから決まる
これもまた pTeX 系列と LuaTeX-ja の違いです。どちらが良いかと言われると微妙ですが,LuaTeX-ja では「リガチャの構成要素を基準に禁則ペナルティの値を決定する」という方向で一貫している,という見方もできそうです。
おわりに
本稿では,ペナルティに注目して pTeX と LuaTeX-ja の挙動を比較してきました。pTeX 系列用に書かれた設定コマンドを LuaTeX-ja 用に書き換えるのは,そう難しくはありません。しかし,その設定がどこに効いているか・意図した設定がきちんと組版結果に反映されているかどうかは,一度確認してみると良いのではないでしょうか。