和文組版の詳細(縦組・ルビ・禁則)

日本語の行には単語間のスペースがありません。それなのに、LaTeX で組んだ和文はきちんと両端が揃います。種明かしは \kanjiskip という接着剤で、自然幅は 0pt、しかし伸びる——jsarticle の 10pt 実測で 0.0pt plus 0.92473pt minus 0.0924pt です。ふだんは存在しないのに、行を詰めたり伸ばしたりする段になると全ての文字のあいだから少しずつ湧いてくる。欧文の組版が単語間の空きに任せている仕事を、日本語では 文字と文字のあいだ全部 に薄く配分しているわけです。このページでは、この和文グルーと、行頭行末の禁則処理、縦組、ルビという、TeX が日本語のために増設した仕掛けを実測値つきで見ていきます。

\kanjiskip\xkanjiskip — 打っていない空白が入る仕組み

和文組版で自動的に入るグルーは二種類あります。\kanjiskip は和文文字どうしのあいだ\xkanjiskip は和文と欧文の境目 です。決定的に違うのは自然幅で、jsarticle の 10pt では \kanjiskip0.0pt plus 0.92473pt minus 0.0924pt\xkanjiskip2.5pt plus 1.49994pt minus 0.59998pt。前者は「ふだん幅ゼロ、必要なときだけ伸びる」——だから字送りは崩れないのに行は揃う。後者は「常に 2.5pt ある」——だから「日本語 and abc」と書くと、スペースを打っていないのに英単語の前後が離れます。この二つを知っていると、和文の行間・字間の相談ごとの大半は「どちらのグルーの話か」に還元できます。

latex
% measure it yourself: put this in the body and read the .log
\typeout{kanjiskip  = \the\kanjiskip}
\typeout{xkanjiskip = \the\xkanjiskip}
\sbox0{日本語テキスト}\typeout{plain = \the\wd0}
\sbox0{日本語ABCテキスト}\typeout{mixed = \the\wd0}
\sbox0{ABC}\typeout{latin = \the\wd0}

実際に測るとこうなります。jsarticle の 10pt で 日本語テキスト64.72778ptABC21.8056pt。単純に足せば 86.53338pt のはずですが、日本語ABCテキスト の実測は 91.53339pt です。差の 5.00001pt は、\xkanjiskip の 2.5pt が A のあいだ、C のあいだの 二か所 に入ったぶん。証拠に \noautoxspacing を宣言してから同じ箱を測ると 86.53339pt に戻り、足し算どおりになります。もうひとつ、日本語 の実測は 27.74048pt で、漢字一文字 9.24683pt のちょうど三倍です——文字と文字のあいだに \kanjiskip が入っているのに、幅が増えていない。自然幅が 0 だからです。

パラメータpTeX 系での書き方LuaTeX-ja での書き方jsarticle 10pt の既定値
kanjiskip\kanjiskip=...\ltjsetparameter{kanjiskip=...}0.0pt plus 0.92473pt minus 0.0924pt
xkanjiskip\xkanjiskip=...\ltjsetparameter{xkanjiskip=...}2.5pt plus 1.49994pt minus 0.59998pt
prebreakpenalty\prebreakpenalty〈文字〉=...\ltjsetparameter{prebreakpenalty={〈文字〉,...}}句読点・閉じ括弧で 10000
postbreakpenalty\postbreakpenalty〈文字〉=...\ltjsetparameter{postbreakpenalty={...}}開き括弧で 10000
jcharwidowpenalty\jcharwidowpenalty=...\ltjsetparameter{jcharwidowpenalty=...}500
inhibitxspcode\inhibitxspcode〈和文字〉=0..3\ltjsetparameter{jaxspmode={...}}全角開き括弧 2、閉じ括弧・句点 1、漢字 3
xspcode\xspcode〈欧文字〉=0..3\ltjsetparameter{alxspmode={...}}A3.2(1

この既定値はクラスによって変わり、そこが版面の性格を決めます。jlreq で同じ測定をすると、\kanjiskip0.0pt plus 2.5pt\xkanjiskip2.5pt plus 2.5pt minus 1.25ptjlreq の 10pt では漢字がきっかり 10pt なので、伸び幅は 全角の 0.25 倍 ということになります——jsclasses の 0.1 倍の 2.5 倍 です。つまり jlreq のほうが字間を大きく伸ばして行を揃えにいく設計で、行末の追い込み・追い出しの挙動が目に見えて変わります。「クラスを変えたら組版の印象が変わった」と感じたら、まずこの二つの値を \typeout で出して比べてください。

全角括弧の周りに空きが入らないのはなぜか — 全角・半角と \inhibitxspcode

括弧そのものが「こちら側には空きを入れるな」と宣言しているからです。\inhibitxspcode は和文文字ごとに \xkanjiskip の入りかたを 0〜3 で指定するもので、jsarticle の既定では全角の開き括弧 2、閉じ括弧 と句点 1、ふつうの漢字が 3 です。効果は測ればわかります——日本語(ABC)テキスト の実測は 105.02704pt、これは全角 9 文字分 (9 × 9.24683pt) と ABC の 21.8056pt をそのまま足した値で、\xkanjiskip一か所も入っていません。全角括弧はもともと左右に余白を含んだ字形なので、そこにさらに 2.5pt を足すと明らかに空きすぎる。だから括弧の側で禁止しているわけです。英字と数字は半角、和文の句読点と括弧は全角 という原稿の慣習を守ることには、見た目だけでなくこの空き調整が正しく効くという実利があります。半角の ( を和文に混ぜると \xspcode の側の規則((1)が働き、まったく違う空きかたになります。

latex
% (u)pLaTeX: inspect and change how xkanjiskip attaches to one character
\typeout{open  paren = \the\inhibitxspcode`(}   % 2 in jsarticle
\typeout{close paren = \the\inhibitxspcode`)}   % 1 in jsarticle
\inhibitxspcode`(=3   % allow the gap on both sides after all

% LuaLaTeX: same idea, other name -- and NO space after the comma
\ltjsetparameter{jaxspmode={`(,preonly}}

ここに、pLaTeX と LuaLaTeX のあいだで設定を移植するときの落とし穴があります。\inhibitxspcode の 1 と 2 は、LuaTeX-ja の jaxspmode では逆の番号になっています。 実測で確かめられます。pTeX 側で漢字 語 の \inhibitxspcode を 1 にすると A語 から空きが消えて(19.24684pt → 16.74684pt)語A には残ります。ところが LuaTeX-ja で同じ字の jaxspmode を 1 にすると 逆に 語A から消えて(19.24713pt → 16.74713pt)A語 に残ります。LuaTeX-ja のソースを見ると jaxspmode の 1 は preonly、2 は postonly という名前が付いていて、意味は「その和文文字の前だけ許す/後ろだけ許す」。pTeX の 1 は「後ろだけ許す」なので、言葉としては同じ体系なのに数字だけが入れ替わっている わけです。設定を機械的にコピーすると、空きが入る側が反対になります。移植するときは数字ではなく preonly / postonly という名前で書いておくのが安全です。ただし LuaTeX-ja では カンマの直後に空白を入れてはいけません。空けると TeX Live 2024 は ! Missing number, treated as zero. を出したうえで、そのまま 0 を適用します——片側どころか両側の空きが消えます。

禁則処理 — \prebreakpenalty\postbreakpenalty

禁則とは、特定の文字を行頭・行末に来させない規則です。句読点 。、 と閉じ括弧 )」』、それに小書き仮名 ゃ ぁ行頭に来てはいけない(行頭禁則)、開き括弧 (「『行末に来てはいけない(行末禁則)。どの文字がどちら側を嫌うかは和文フォントメトリック(JFM)とクラスが与え、TeX はこれを禁止ではなく ペナルティ として実装しています。句点 や閉じ括弧 の直前で改行すると、その文字が次の行の頭に落ちてしまう——だから jsarticle では \prebreakpenalty。 も \prebreakpenalty) も 10000 に設定されています。逆に開き括弧 の直後で切ると括弧が行末に取り残されるので、\postbreakpenalty( が 10000。TeX の慣習で 10000 は「無限大のペナルティ」、つまり事実上の禁止です。この作りの利点は、禁則を段階的にできる ことにあります。「絶対に避けたい」なら 10000、「なるべく避けたい」なら 200 のように書けて、行分割アルゴリズムが他の要素と天秤にかけてくれる。欧文の組版に禁則という概念そのものが無いので、これは pTeX が TeX に増設した機構です。

latex
% read the shipped values, then relax one of them
\typeout{full stop  = \the\prebreakpenalty`。}   % 10000
\typeout{open paren = \the\postbreakpenalty`(}  % 10000
\prebreakpenalty`〜=200        % discourage, do not forbid
\jcharwidowpenalty=1000        % dislike a lone character on the last line

% the LuaTeX-ja spelling of the same three settings
\ltjsetparameter{prebreakpenalty={`〜, 200}}
\ltjsetparameter{jcharwidowpenalty=1000}

ペナルティで書かれているおかげで、日本語の組版には欧文に対応物のない専用の項目まで用意できました。\jcharwidowpenalty がそれで、jsarticle でも ltjsarticle でも既定値は 500。これは「段落の最終行に和文の文字が一つだけ取り残される」——欧文の widow / orphan の和文版——のを嫌う重みです。500 という中庸な値なのが要点で、絶対に禁止するのではなく「他の行を少し詰めてでも避けたい、ただし版面を壊すほどではない」という強さに置かれています。実務では、どうしても一文字残る段落に出くわしたとき、原稿を書き換える前に \jcharwidowpenalty=1000 を試すのが安全な手当てです。それでも直らないなら、その段落は文章のほうを一文字ぶん増減させたほうが早いこともあります。

縦書き(縦組)をどう指定するか — tate オプションと plext

クラスオプションで切り替えるのが今の作法です。\documentclass[tate]{jlreq}uplatex でも lualatex でも通り、\documentclass[tate]{ltjsarticle} は LuaLaTeX で通ります。pLaTeX 系には縦組専用クラスの utarticleutbook もあり、これも uplatex でそのままコンパイルできます。縦組が TeX にとって特別なのは、行が進む方向と文字が積まれる方向が入れ替わるだけでなく、同じ文字が別の字形を要求する からです。句読点も括弧も、縦組では 90 度回した位置・向きに置き直さなければなりません。だから pTeX には横組用(JY)と縦組用(JT)という二系統のフォントエンコーディングが最初から用意されていて、\tate を宣言した箱の中では自動的に JT 側が使われます。欧文の組版には対応する概念がまったく無い部分です。

latex
% vertical writing, three ways that all compile on TeX Live 2024
\documentclass[tate,uplatex]{jlreq}     % uplatex; also works under lualatex
\documentclass[tate]{ltjsarticle}       % lualatex
\documentclass[uplatex]{utarticle}      % uplatex, dedicated vertical class

% tate-chu-yoko: a short horizontal run inside vertical text
平成\tatechuyoko{31}% jlreq only -- under uplatex or lualatex
平成\rensuji{31}% plext (pLaTeX) and lltjext (LuaTeX-ja)

% zw is the width of one full-width character, zh its height;
% \begin{minipage}<t>{10zw} sets a vertical box ten characters wide

縦組の中に二桁の数字や単位を 正立させて横に並べる 組み方を 縦中横(たてちゅうよこ) と呼びます。綴りは経路ごとに違います。\tatechuyoko{31} を定義しているのは jlreq だけで、jlreq を使っていれば uplatex でも lualatex でも動きます。ltjsarticle には無いので、そこで \tatechuyoko と書くと ! Undefined control sequence. になります。LuaTeX-ja 側の綴りは lltjext(または縦組専用の ltjtarticleltjtbook)が与える \rensuji{31}、pLaTeX 系は plext パッケージの \rensuji{31} です。plext はもともと縦組のために ASCII が用意した拡張で、\rensuji のほかに 表・箱・並べ方向の指定 を担当します——\begin{minipage}<t>{10zw} のように山括弧で組方向を明示できるのが plext の顔で、縦組の文書に横組の図表を差し込むときにこれが要ります。LuaTeX-ja 側の対応物が lltjext で、名前も引数の作法もほぼ写しになっています。縦組で困ったら、まず「そのクラスに tate オプションがあるか」、次に「plextlltjext を読んでいるか」を確認するのが早道です。

ルビ(ふりがな)と圏点 — どのパッケージを選ぶか

(u)pLaTeX なら pxrubrica、LuaLaTeX なら luatexja-ruby が現在の定石です。\ruby{...}{...} という書きぶりは共通ですが、pxrubrica は JIS X 4051/JLReq が定義するルビの種別を区別し、しかも どちらかを選ばせます。縦棒は読みを一字ずつに割る印で、\ruby{漢字}{かん|じ}モノルビ(漢字一字ごとにルビを対応させる)。熟語全体に一続きのルビを振る グループルビ\ruby[g]{漢字}{かんじ} と明示的に頼む必要があり、縦棒もオプションも付けずに二字の熟語へルビを振ると ! Package pxrubrica Error: Group count mismatch between the ruby and the body (2 <> 1). で止まります。圏点(傍点)も pxrubrica\kenten{重要} で付けられます。ごく軽く済ませたいなら jsclasses 付属の okumacro\ruby でも十分です——こちらは常にグループルビなので \ruby{漢字}{かんじ} がそのまま通ります。ここで注意が要るのは、CTAN に ruby という名前のパッケージも別に存在する ことで、これは Werner Lemberg 氏による CJK パッケージ側の ruby(TeX Live 2024 同梱版は 4.8.5)——出自の違う別系統です。

パッケージ経路何ができるか
pxrubricapLaTeX / upLaTeX / LuaLaTeX / XeLaTeXモノルビとグループルビの区別、ルビの掛かり方の制御、\kenten による圏点
luatexja-rubyLuaLaTeX のみLuaTeX-ja に統合されたルビ。行分割と干渉しにくい
okumacropLaTeX / upLaTeXjsclasses 付属。\ruby\kenten を手早く。分割記法は無い
rubyCJK パッケージ側(pdfLaTeX など)Werner Lemberg 氏による別系統。日本語 TeX 用ではないので混ぜない
latex
\usepackage{pxrubrica}
...
\ruby{漢字}{かん|じ}     % mono ruby: the bar splits the reading per character
\ruby[g]{漢字}{かんじ}   % group ruby: one run over the word -- [g] is required
\kenten{重要}            % emphasis dots beside each character

ここで一つ、エラーが出ないまま壊れる 事故を紹介します。pxrubricaokumacro はどちらも \ruby を定義しますが、両方を読み込んでも警告すら出ません。後に読み込んだほうが黙って上書きするだけです。実際に pxrubrica の後で okumacro を読み込んで \ruby{漢字}{かん|じ} を組むと、かん|じ の縦棒が モノルビの区切りとして解釈されず、全角の縦線としてそのまま印刷されますpxrubrica 単独なら かん に分かれて載ります)。ログには何も出ないので、PDF を目で見るまで気づけません。\ruby を定義するパッケージは 一つだけ読み込む こと。jsclasses を使っていて okumacro を読んでいるなら、pxrubrica に切り替えるときは okumacro の行を消してください。

JLReq とは何か — W3C「日本語組版処理の要件」と jlreq クラス

JLReq は W3C が公開している「日本語組版処理の要件(Requirements for Japanese Text Layout)」 という文書で、日本語の版面設計・行組版・禁則・ルビ・約物の扱いを、実装者が読める形で体系的に書き下したものです。日本語の組版知識はそれまで印刷所の経験知として分散していたので、これが公開された意味は大きく、Web ブラウザから電子書籍リーダーまでが同じ文書を参照するようになりました。jlreq クラスはこの文書を LaTeX 上で実装したもので、作者は Noriyuki Abe 氏。TeX Live 2024 同梱版のクラスファイル冒頭には (based on JLReq 20200811) と、参照した版が明記されています。つまり jlreq を使うという選択は、単に「新しいクラスを選ぶ」ことではなく、版面の根拠を公開文書に置く ということです。

実務上の帰結はこうです。細かい調整を自分で書かなくてよくなります。 縦組は tate オプション一つ、\jlreqsetup で行組版のパラメータを名前で指定でき、禁則やルビの既定値も JLReq に従った値で入っています。逆に、既存の jsarticle 文書をそのまま jlreq に差し替えると、\kanjiskip の伸び幅が広がるぶん行の詰まり方が変わり、ページ数まで動くことがあります。新規に始める文書なら jlreq、既存の原稿の見た目を保ちたいなら jsclasses のまま というのが穏当な線引きです。どちらを選んでも、このページで見た \kanjiskip\xkanjiskip\prebreakpenalty\jcharwidowpenalty は同じ名前で存在しているので、最後の微調整はいつでも自分の手で効きます。