Acetaminophen’s diary

化学に関すること,TeXに関すること,ゆきだるまに関すること。

pTeX のペナルティを LuaTeX-ja で試してみた

これは TeX & LaTeX Advent Calendar 2019 の 15 日目の記事です。昨日は hak7a3 さんでした。明日は keisuke495500 さんです。

今年は残念ながら TeXConf 2019 が中止になってしまいました。私は「pTeX のペナルティ」という題目で一般講演を申し込んでいたのですが,発表スライドを使う予定がなくなったのでここで公開します。

…さて,TeXLaTeX Advent Calendar 2019 の重点テーマは「とにかく Lua(La)TeX しよう」です。pTeX ではありません。というわけで,先のスライドと同じことを LuaTeX (LuaTeX-ja) でやってみるとどうなるか,試してみることにしましょう。

f:id:acetaminophen:20191215120823p:plain

おことわり:本稿はあくまで実験結果で,何らかの結論を導くものではありません。「ただやってみた」という程度に捉えてください。

 

前提

以下,例のスライドの PDF におけるページ番号に対応しながら見ていきます。まず p.3 の「前提」です。

f:id:acetaminophen:20191215121010p:plain

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 でどう実装されているかを説明しています。

f:id:acetaminophen:20191215121350p:plain

f:id:acetaminophen:20191215121727p:plain

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 では和文文字の禁則ペナルティはその文字ノードをリストに追加するタイミングと同時に挿入されます。

f:id:acetaminophen:20191215132013p:plain

一方,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 では成り立たないことになります。

f:id:acetaminophen:20191215133625p:plain

代わりに,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 ではどうなのでしょうか。順に見ていきましょう。

f:id:acetaminophen:20191215122245p:plain

スライドには載せませんでしたが,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 にはアレ🍣な挙動が存在します。

f:id:acetaminophen:20191215141441p:plain

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 に書かれているややこしい話です。

f:id:acetaminophen:20191215143018p:plain

改めて 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 系列では

  • \postbreakpenalty はリガチャ自体の文字コードから決まる
  • \prebreakpenalty はリガチャ自体ではなく,それを構成する最初の要素文字コードから決まる

という挙動を示します。一方,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 用に書き換えるのは,そう難しくはありません。しかし,その設定がどこに効いているか・意図した設定がきちんと組版結果に反映されているかどうかは,一度確認してみると良いのではないでしょうか。