いものやま。

雑多な知識の寄せ集め

TeXで同人誌を作ってみた。(目次デザイン)

f:id:yamaimo0625:20190918075457j:plain

今日も \TeXの話で、目次のデザインについて。
なお、 \TeX \LaTeXは区別せずに扱ってる。

参考にしたのは以下:

LaTeX2ε辞典 増補改訂版 (DESKTOP REFERENCE)

LaTeX2ε辞典 増補改訂版 (DESKTOP REFERENCE)

Before / After

まずは変更前と変更後でどう変わったのかを示しておきたい。

変更前はこちら:

f:id:yamaimo0625:20190920121846p:plain

なお、昨日の見出しデザインの変更が入ってるので、目次の見出しは章見出しと同じデザインになってる。
あと、目次にリンクをはる変更が入ってる。

変更後はこちら:

f:id:yamaimo0625:20190920122127p:plain

以下が変わってることが分かると思う:

  • 目次の見出しデザインが変わってる
  • 小節まで見出しが出るようになっている
  • 章番号がない章のタイトルの先頭が章番号がある章のタイトルの先頭に揃っている
  • メインコンテンツでは章の上に線が引かれている
  • 節は章のタイトルの先頭にインデントされ、小節は節のタイトルの先頭にインデントされている
  • 「はじめに」の上に大きくスペースが入っている

「メインコンテンツでは・・・」というのは、「はじめに」や「プロローグ」も章なんだけど、これらの非メインコンテンツでは章の上に線が引かれず、「第1章」「第2章」のようなメインコンテンツでは章の上に線が引かれているということ。

ちなみに、これも『数学ガール』のデザインをマネたものとなっている。

目次の見出しデザインの変更

まずは目次の見出しデザインの変更から。

目次は\tableofcontents命令で出力するのだけど、これで見出しなどもまるまる出力され、目次の場合は「目次」という章見出しが出力される定義になっている。

章見出しが出ること自体はいいんだけど、そのデザインは他の章見出しとは違う特別なものにしたい。
そういう場合、グルーピングを使うといい。

 \TeXでは{}で囲うことでグルーピングでき、その内側はローカルになって外側の定義に影響を与えずに変更することが出来る。

そこで、以下のようにグルーピングしてその中で章見出しのデザインを変えてから目次を出力するようにした:

% 目次
{
% 目次の見出しだけデザインを変える
\makeatletter
\renewcommand{\@makeschapterhead}[1]{% #1: 見出し
    \vspace*{-2\baselineskip}%
    \noindent\hspace*{0.15\textwidth}\hrulefill\par%
    \vtop to 3\baselineskip{% 3行分の高さを確保
        \noindent\hspace*{0.15\textwidth}%
        \textsf{\large \kintou{0.85\textwidth}{C O N T E N T S}}\par%
    }\par}
\makeatother
\tableofcontents
}

章のタイトルの先頭と揃うように、テキスト幅(\textwidth)の右側85%が目次の見出しになるようにして、そこに「CONTENTS」の文字を均等割りで配置している。

目次レベルの変更

目次でどの見出しレベルまで出すかは、tocdepthというカウンターの値で指定する。

% 目次レベル
\setcounter{tocdepth}{2}  % 小節まで目次に出す

目次の章のデザイン変更

目次での章のデザインを変えるには、\l@chapterという命令を再定義する。

オリジナル(bxjsbook.cls)では次のようになっている:

\newcommand*{\l@chapter}[2]{%
  \ifnum \c@tocdepth >\m@ne
    \addpenalty{-\@highpenalty}%
    \addvspace{1.0em \@plus\p@?}
    \begingroup
      \parindent\z@
      \rightskip\@tocrmarg
      \parfillskip-\rightskip
      \leavevmode\headfont
      \setlength\@lnumwidth{\jsc@tocl@width}\advance\@lnumwidth 2.683\jsZw
      \advance\leftskip\@lnumwidth \hskip-\leftskip
      #1\nobreak\hfil\nobreak\hbox to\@pnumwidth{\hss#2}\par
      \penalty\@highpenalty
    \endgroup
  \fi}

うん、さっぱり分からないw
一応、カウンターのtocdepthの値を比較して出力するかどうか決めてるのかなぁくらいは分かるけど、あとは \TeXのモードとか(水平モードとか垂直モードとか)ペナルティの話が分かってないと厳しそう。
(そして自分はまだそこらへんを理解しきれてない)

いろいろ試したりしつつ、以下のようにしてみた:

\makeatletter

% 目次の章の体裁カスタマイズ
\newif\ifmaincontents   % メイン内の章かを区別するため
\maincontentsfalse
\renewcommand{\l@chapter}[2]{%
    \ifnum \c@tocdepth >\m@ne
        \setlength\@lnumwidth{0.15\textwidth}
        \ifmaincontents
            \noindent\hrulefill\par%
            \nobreak%
            \noindent {\large #1\hfill #2}\par
            \vspace*{0.5\baselineskip}
        \else
            \noindent {#1\hfill #2}\par
            \vspace*{0.5\baselineskip}
        \fi
    \fi}

\makeatother

まず、メインコンテンツかどうかでデザインを変えたいので、\ifmaincontentsという条件判断を用意している。
これについては後述。

そして、\@lnumwidthをテキスト幅の15%にしてるので、たしかこれがタイトルまでのインデント幅。

あとは、メインコンテンツなら横線を引いてから出力し、そうでなければ単に出力をしている。

目次の節、小節のデザイン変更

目次の節や小節のデザインを変えるには、\l@section\l@subsectionの定義を変更する。

これらのオリジナル(bxjsbook.cls)では定義がどうなっているかというと以下:

\newcommand*{\l@section}{%
          \@tempdima\jsc@tocl@width \advance\@tempdima -1\jsZw
          \@dottedtocline{1}{\@tempdima}{3.683\jsZw}}
\newcommand*{\l@subsection}{%
          \@tempdima\jsc@tocl@width \advance\@tempdima 2.683\jsZw
          \@dottedtocline{2}{\@tempdima}{3.5\jsZw}}

長さを計算して実際の処理は\@dottedtoclineという命令に委譲してるのが分かる。
この命令は\@dottedtocline{<tocdepth>}{<margin>}{<numlength>}という感じで、目次レベルとマージンサイズ、それと番号が出力される部分の長さを指定するようになっている。

これは次のように変更してみた:

\makeatletter

% 目次の節の体裁カスタマイズ
\renewcommand{\l@section}{\@dottedtocline{1}{0.15\textwidth}{2.5em}}
% 目次の小節の体裁カスタマイズ
\renewcommand{\l@subsection}{\@dottedtocline{2}{0.235\textwidth}{3.5em}}

\makeatother

なお、0.235\textwidthというのは目で見てそれっぽくなってる値にしただけ(^^;
ちゃんとやるなら足し算しないとダメ。

章番号なしの章の目次への追加

\chapter*{}を使った章は章番号が出ないだけじゃなくて目次にも出てこない。
これを目次に出すようにするには\addcontentlineという命令を使う。

例えば、「はじめに」だとこんな感じ:

\chapter*{はじめに}
\addcontentsline{toc}{chapter}{はじめに}

これで目次に章として「はじめに」が追加される。

ただ、これだと変更前の画像で見たとおり、章番号と同じインデントの深さにタイトルが出てしまう。
章番号のある章のタイトルと同じインデントの位置にタイトルは出て欲しい。

そこで以下のように変更してみた:

\chapter*{はじめに}
\addcontentsline{toc}{chapter}{\protect\numberline{\null}はじめに}

どうしてこれでうまくいくのかを以下で説明していく。

まず、 \TeXは目次をtocファイルから作るのだけど、上記のように変更せずにtocファイルを見てみると次のようになっている:

\contentsline {chapter}{はじめに}{i}{chapter*.1}% 
...
\contentsline {chapter}{\numberline {第1章}ポーカーのルール}{1}{chapter.1}% 
\contentsline {section}{\numberline {1.1}テキサス・ホールデム}{2}{section.1.1}% 
...

つまり、章番号や節番号の部分は\numberlineという命令によって出力されていることが分かる。
なら、空の章番号を\numberlineで出力するようにすれば、インデント位置が揃うはずと。

ただし、単に\addcontentsline{toc}{chapter}{\numberline{\null}はじめに}とするとうまくいかない。
これは\numberline{\null}という命令がtocファイルに書き込まれる前に評価されて別の文字列に変わってしまうから。
それを抑制するために\protectが付け加えられている。

上記の変更をしたあとのtocファイルの出力はこうなる:

\contentsline {chapter}{\numberline {\hbox {}}はじめに}{i}{chapter*.1}% 
...
\contentsline {chapter}{\numberline {第1章}ポーカーのルール}{1}{chapter.1}% 
\contentsline {section}{\numberline {1.1}テキサス・ホールデム}{2}{section.1.1}% 

これで章番号がない章のタイトルの先頭も章番号がある章のタイトルの先頭に揃うようになる。

メインコンテンツ/非メインコンテンツの切り替え

上記までの変更を行なっての出力が以下:

f:id:yamaimo0625:20190920135535p:plain

メインコンテンツと非メインコンテンツの切り替えが出来ていないので、「第1章」の上に線が引かれてない。
あと、「はじめに」の前の空きも足りてない。

そこで、tocファイルに「非メインコンテンツの開始」「メインコンテンツの開始」の命令を書き込むようにした:
(どうやって書き込むのかは後述)

before

\contentsline {chapter}{\numberline {\hbox {}}はじめに}{i}{chapter*.1}% 
...
\contentsline {chapter}{\numberline {第1章}ポーカーのルール}{1}{chapter.1}% 
\contentsline {section}{\numberline {1.1}テキサス・ホールデム}{2}{section.1.1}% 

after

\startnomaintoc 
\contentsline {chapter}{\numberline {\hbox {}}はじめに}{i}{chapter*.1}% 
...
\startmaintoc 
\contentsline {chapter}{\numberline {第1章}ポーカーのルール}{1}{chapter.1}% 
\contentsline {section}{\numberline {1.1}テキサス・ホールデム}{2}{section.1.1}% 

そして、このtocファイルに書き込んだ命令を以下のように定義することで、メインコンテンツ/非メインコンテンツの切り替えを行い、また非メインコンテンツの前には空きを入れるようにした:

% \startmaintocで\ifmaincontentsがtrueになるようにする
\newcommand{\startmaintoc}{\maincontentstrue}
% \startnomaintocで\ifmaincontentsがfalseになるようにし2行分の空きを入れる
\newcommand{\startnomaintoc}{\maincontentsfalse\vspace*{2\baselineskip}}

あとはこの\startmaintocおよび\startnomaintocをどうやってtocファイルに書き込むか。

調べてみると\addaddcontentslineは以下のように「tocファイルに目次を書け」という命令をauxファイルに書き込んでいるようだった。

% latex.ltxから引用(コメントは自分が追記)

\def\addcontentsline#1#2#3{%
  \addtocontents{#1}{\protect\contentsline{#2}{#3}{\thepage}%
                     \protected@file@percent}}
\long\def\addtocontents#1#2{%
  \protected@write\@auxout% ここがauxへ書けという命令
      {\let\label\@gobble \let\index\@gobble \let\glossary\@gobble}%
      {\string\@writefile{#1}{#2}}}% auxに書く内容は「#1(toc)に#2を書け」という命令

そして、auxファイルが評価されることでtocファイルに目次が書き込まれる(そして\tableofcontents命令はtocファイルの内容を実行することで目次を出力する)。

なので、同じように「tocファイルに\startmaintocを書け」という命令をauxファイルに書き込む命令を作って、それを呼び出せばいい。

そこでいろいろ調べながら次のような命令を用意した:

\newcommand{\startmaincontents}{%
    \immediate\write\@auxout{\string\@writefile{toc}{\string\startmaintoc}}}
\newcommand{\startnomaincontents}{%
    \immediate\write\@auxout{\string\@writefile{toc}{\string\startnomaintoc}}}

あとは以下のように呼び出すだけ:

\startnomaincontents

\chapter*{はじめに}
\addcontentsline{toc}{chapter}{\protect\numberline{\null}はじめに}
\startmaincontents

\chapter{ポーカーのルール}

これで冒頭のような目次が得られた。


宣伝です。
気になった人はチェックをお願いします!

今日はここまで!