LaTeXのlistで段落見出しをやろうとしてはいけない

公開:2020/10/30
更新:2020/10/30
2 min読了の目安(約2600字TECH技術記事

LaTeXの \item\@item を使って定義されているが、\@item では、箇条書きの「見出し」の出し方がトリッキーな方法で実現されており、そのため list 環境で作る箇条書き環境で見出しを段落として整形する(いわゆる「段落見出し」)ことには限界がある、という話。

LaTeXの箇条書きでは各項目を \item で表現するのだけど、ユーザーレベルではその見出しの内容や装飾を \makelabel を使って制御できるようになっている。\@item の定義では、下記のように、\makelabel で整えられた見出しの材料がいったん \box\@labels というボックスに格納されることになっている。このボックスは、やはり \@item の中で用意されている \everypar によって段落の先頭に追加されることで、最終的に組版されるという仕掛け。

\def\@item[#1]{%
  \if@noparitem
    \@donoparitem
  \else
    \if@inlabel
      \indent \par
    \fi
    \ifhmode
      \unskip\unskip \par
    \fi
    \if@newlist
      \if@nobreak
        \@nbitem
      \else
        \addpenalty\@beginparpenalty
        \addvspace\@topsep
        \addvspace{-\parskip}%
      \fi
    \else
      \addpenalty\@itempenalty
      \addvspace\itemsep
    \fi
    \global\@inlabeltrue
  \fi
  \everypar{%
    \@minipagefalse
    \global\@newlistfalse
    \if@inlabel
      \global\@inlabelfalse
      {\setbox\z@\lastbox
       \ifvoid\z@
         \kern-\itemindent
       \fi}%
      \box\@labels
      \penalty\z@
    \fi
    \if@nobreak
      \@nobreakfalse
      \clubpenalty \@M
    \else
      \clubpenalty \@clubpenalty
      \everypar{}%
    \fi}%
  \if@noitemarg
    \@noitemargfalse
    \if@nmbrlist
      \refstepcounter\@listctr
    \fi
  \fi
  \sbox\@tempboxa{\makelabel{#1}}%
  \global\setbox\@labels\hbox{%
    \unhbox\@labels
    \hskip \itemindent
    \hskip -\labelwidth
    \hskip -\labelsep
    \ifdim \wd\@tempboxa >\labelwidth
      \box\@tempboxa
    \else
      \hbox to\labelwidth {\unhbox\@tempboxa}%
    \fi
    \hskip \labelsep}%
  \ignorespaces}

TeXは \item に遭遇すると、まず @inlabel フラグをオンにする(厳密にいえば \@noparitem フラグが立っているときは @inlabel が立つことはないが、ltlists.ltx\@noparitem が立つように設定されているのは trivlist くらいっぽい?)。

@inlabel フラグがオンの場合には、\lastbox を捨てて(つまりインデントを帳消しにして)垂直方向のグルーを挿入したあと、\box\@labels を配置する。そのあとではじめて、\item に続いて原稿中で示されているトークンの存在によって水平モードに入る。

このような挙動のため、\item の「見出し」では水平モードに入っておらず、厳密には段落が始まっていない。したがって、\makelabel でいわゆる「段落見出し」を作って独自の list を定義した場合、\item[...] の直後(i.e. 水平リストを開始する要素を挟まない)に itemize 環境を入れると、内側の itemize 環境の \item の見出しが適切に「改行」されずに版面を超えて前の行に残ってしまうという事態になる。

そもそも「段落見出しが必要な場合に list 環境を使うのはどうなのか」という話でもあるんだけれど、HTMLの <DL> のような「見出し付き箇条書き」はドキュメントの要素として一般によく使われており、その見出しの表現として「段落見出し」を使いたいのも自然に思える。こうした見出し付き箇条書きをLaTeXで表現するときにいちばん直感的なのは description らしく、実際、Pandocでもそのように実装されている。description は標準的には list で定義されているので、「list で段落見出しの箇条書き環境を定義したい」というのは、それほど無茶な要望ではないと思われる。

ワークアラウンド

\item[...] で水平リストを明示的に開始すればいい。Pandocならこんな感じ。

item 1
: `\leavevmode\vspace{-\baselineskip}`{=latex}
    
    * subitem 1
    * subitem 2

item 2
: ...