これはTeX & LaTeX Advent Caleandar 2017の四日目です.
何かTeXで作ろう,という話だったので,行取り用のマクロを作った話.
何をする?
適当な行数を確保し,その中に出力したい文を配置することを行取りという.例えば二行取りに一行のものを入れるならば,前後に半行分の空きが余分に入ることになる.JLReqの4.1.6「行取りの処理例」を参照のこと.jlreq.clsの別行見出しには実装されているし(\jlreq@BlockHeading@start@gyodori
),ググるといくつか引っかかる.以下,用語は横書きのもの.(行送り方向を「縦」と呼ぶ.)
TeXでやる場合,出力したいものの行送り方向の長さを計算し,それに応じて空きをいれることになる.つまりおおざっぱなコードは
\def\gyodori#1#2{% #2を#1行取り
\setbox\@tempboxa=\vbox{#2}%
\@tempdima=\dimexpr(... - \dp\@tempboxa - \ht\@tempboxa)/2\relax % 全体の長さから#2の長さを引いて半分にする.
\vspace{\@tempdima}%
\box\@tempboxa
\vspace*{\@tempdima}%
}
となる.(以下e-TeX拡張は常に仮定する.)ただし,TeXは様々な場所に自動で空きを挿入するので,このような単純なコードではうまくいかない.
TeXの挿入する空き
行間
TeXで通常通りに文章を入力すると,行の間には適当な空きが入る.これを実行するためにTeXは次のように空きを入れる.TeXはボックスを並べて組版を行っていることを思いだそう.特に,各行は一つ一つが水平ボックスである.各ボックスは空きを持ち,それより上の長さを「高さ」,それより下の長さを「深さ」と呼ぶ.\baselineskip
から前の行の深さと次の行の高さを引いたものを考える
- この値が
\lineskiplimit
より大きい場合,TeXは縦方向にこの長さの空きを入れる.これにより,各行の参照点の間の長さは常に\baselineskip
となる. \lineskiplimit
より小さい場合は,長さ\lineskip
の空きが入る.
前の行の深さは\prevdepth
で参照できる.またこの値はTeXコードにより書き換えることもできる.\prevdepth=-1000pt
の時に限り一切の空きが入らない.
ページ頭
ページ頭には,\topskip
から最初の行の高さを引いただけの空きが入る.これによりすべてのページの一行目の参照点がすべて同じ高さにそろう.ただしこの値が負の時は空きは入らない.
行取りマクロその1
$n$行取りするとし,次のように変数を設定しておく.
- 通常の文字(青い箱)の高さを$h$,深さを$d$とする.
- 前の行,後ろの行,行取りしたいものの高さをそれぞれ$h_1,h_2,H$,深さをそれぞれ$d_1,d_2,D$とする.$d_1$は
\prevdepth
である. - 行取りしたいものの縦方向の長さを$L$とする.$L = D + H$.
\baselineskip
を$p$とする.
さらに以下の図($n = 3$の場合)のように変数を設定する.
「この間に配置する」と書いてある部分のど真ん中に行取りしたいものがおかれる.まずは$p - (d_1 + H)$が\lineskiplimit
より大きいとしておこう.すると,TeXが自動挿入する空きの効果で,図の中に書いてある参照点の上下方向の位置は一致する.従って,図の$x$の空きを入れれば行取りされたことになる.
図より$s + H + D + s = l$であるので, \[ s = \frac{l - (H + D)}{2} \] である.$L = H + D$から \[ s = \frac{l - L}{2}. \] 一方$l$は$n$個の行および$(n - 1)$個の行間からなる.行間(図の$g$)は$p - (d + h)$であるから, \[ l = n(d + h) + (n - 1)(p - (d + h)) = (n - 1)p + d + h \] である. 代入して \[ s = \frac{(n - 1)p + d + h - L}{2} \] を得る.さらに図から$h + x = s + H$であるから, \[ x = s + H - h = \frac{(n - 1)p + d - h - L}{2} + H \] と求まる.
もし$p - (p_1 + H)$が\lineskiplimit
より小さい場合は,この$x$に$p - (p_1 + H)$を足し,\lineskip
を引いた値を空きとして入れればよい.
下方向も計算する.今度は参照点に頼らず計算しよう.図の$a$を求めればよい.$b$は以下の図から$b = h_2 - h$である.
従って,
\[
a = s + g - b = s + p - d - h - h_2 + h = s + p - d - h_2
\]
となる.ただしこれはTeXが自動挿入する空きが考慮されていない.自動挿入する空きがどうなるかは,次の行の高さである$h_2$に依存するが,TeXのモデルから次の行の情報を得るのは不可能である.ここでは\prevdepth
を0ptに設定することで,ほぼ常に\baselineskip
由来の空きである$p - h_2$が入ることに期待しよう.($h_2$が大きいときはうまく行かないが,そもそもこの場合は通常通り組んでも行がずれているのだからもう仕方ないとする.)よって入れるべき空きは
\[
a' = a - p + h_2 = s - d
\]
となる.以上をまとめると,TeXのコードは次のようになる.
\def\gyodori#1#2{% #2を#1行取り
\setbox\@tempboxa=\hbox{阿}% 青い箱
\@tempdimb=\ht\@tempboxa % h
\@tempdimc=\dp\@tempboxa % d
\setbox\@tempboxa=\vbox{#2}%
\par
\@tempdima=\dimexpr(#1\baselineskip - \baselineskip + \@tempdimb + \@tempdimc - \ht\@tempboxa - \dp\@tempboxa)/2\relax % s
\ifdim \dimexpr(\baselineskip - \prevdepth - \ht\@tempboxa)\relax > \lineskiplimit\relax
\vspace{\dimexpr\@tempdima + \ht\@tempboxa - \@tempdimb\relax}%
\else
\vspace{\dimexpr\@tempdima - \@tempdimb + \baselineskip - \prevdepth - \lineskip\relax}%
\fi
\box\@tempboxa
\par
\vspace{\dimexpr\@tempdima - \@tempdimc\relax}%
\prevdepth=0pt
}
ページ頭にくる場合の対処
ページ頭にくる場合には,そもそも\vspace
による空きがページ頭に入らない.\vspace*
を使うと余計な空きが入るので,\vbox
の中に空きを入れることにする.つまり,
\vbox{%
\vspace{<正しい空き量>}%
\unvbox\@tempboxa}%
という感じにすればよい.
実はこれだけ考慮しておけば十分.ページ先頭にせよページ途中にせよ,TeXが参照点を一致させようとするのは変わらないので,同じ図で計算ができる.ただし,$H$が$p - d_1$より大きかったり\topskip
より大きかったりする場合は参照点が合わず破綻する.そのために,TeXコードで$H = 0$としておけば,\topskip
由来の空きは必ず入るし,\baselineskip
由来の空きも入る可能性が高い.また,上で行っていた分岐も不要となり少しコードが簡単になる.以上から,最終的なコードは以下のようになる.
\def\gyodori#1#2{% #2を#1行取り
\setbox\@tempboxa=\hbox{阿}% 青い箱
\@tempdimb=\ht\@tempboxa % h
\@tempdimc=\dp\@tempboxa % d
\setbox\@tempboxa=\vbox{#2}%
\par
\@tempdima=\dimexpr(#1\baselineskip - \baselineskip + \@tempdimb + \@tempdimc - \ht\@tempboxa - \dp\@tempboxa)/2\relax % s
\setbox\@tempboxa=\vbox{%
\vspace{\dimexpr\@tempdima - \@tempdimb\relax}% H = 0
\unvbox\@tempboxa
}%
\dp\@tempboxa=\dimexpr\ht\@tempboxa + \dp\@tempboxa\relax
\ht\@tempboxa=0pt % H = 0とする
\box\@tempboxa
\par
\vspace{\dimexpr\@tempdima - \@tempdimc\relax}%
\prevdepth=0pt
}
追記
行取りされるべき全体を一つのボックスとして処理するとこんな感じ.
\def\gyodori#1#2{%
\setbox\@tempboxa=\hbox{阿}%
\@tempdima=\ht\@tempboxa
\@tempdimb=\dp\@tempboxa
% 上記lの高さのボックスに#2を中央配置
\setbox\@tempboxa=\vbox to \dimexpr#1\baselineskip - \baselineskip + \@tempdima + \@tempdimb\relax{\vss #2\vss}%
\dp\@tempboxa=\dimexpr\ht\@tempboxa + \dp\@tempboxa - \@tempdima\relax
\ht\@tempboxa=\@tempdima
\par\box\@tempboxa\par
\prevdepth=\@tempdimb
}
いやーすごい.利用させてもらいました.
返信削除