📝

コンピュータソフトウェアのLaTeXスタイルファイルでlstlistingのキャプションが崩れる問題

2023/10/13に公開

TL;DR

以下のコードをプリアンブルに貼り付けてsourcecode環境を使ってソースコードを表示してください.

\usepackage{listings}
\usepackage{ifthen}

\makeatletter
\let\MYcaption\@makecaption
\makeatother

\usepackage{caption}

\makeatletter
\let\@makecaption\MYcaption
\makeatother

% ソースコードで図番号を切り替えるためにカウンタを定義
\newcounter{sourcecodefigure}
\newcounter{normalfigure}

% カウンタの切り替えマクロ
\newcommand{\switchtosourcecode}{%
    \setcounter{normalfigure}{\value{figure}}
    \setcounter{figure}{\value{sourcecodefigure}}
}

\newcommand{\switchtonormal}{%
    \setcounter{sourcecodefigure}{\value{figure}}
    \setcounter{figure}{\value{normalfigure}}
}

\newcommand\sourcecodeposition{h}
\newenvironment{sourcecode}[1][h]{%
    \begin{figure}[#1]
    \renewcommand\sourcecodeposition{#1}
    \centering
    \captionsetup{name=ソースコード}
    \switchtosourcecode
    \ifthenelse{\equal{\sourcecodeposition}{t}}%
        {\vspace{-1.3zh}} % for 't'
        {\ifthenelse{\equal{\sourcecodeposition}{b}}%
            {\vspace{-2zh}} % for 'b'
            {\vspace{-2zh}} % for 'h' or default
    }
}{%
    \ifthenelse{\equal{\sourcecodeposition}{t}}%
        {\vspace{-1.3zh}} % for 't'
        {\ifthenelse{\equal{\sourcecodeposition}{b}}%
            {\vspace{-1zh}} % for 'b'
            {\vspace{-3zh}} % for 'h' or default
    }
    \switchtonormal
    \end{figure}
}

\lstset{
  basicstyle={\ttfamily},               % 基本:等幅フォント
  identifierstyle={\small},             % 識別子
  commentstyle={\smallitshape},         % コメント:斜体
  keywordstyle={\small\bfseries},       % キーワード:太字
  stringstyle={\small\ttfamily},        % 文字列
  frame={tb},                           % 上部と下部に線を表示
  breaklines=true,                      % 行が長い場合に折り返す
  columns=[l]{fullflexible},            % 列幅を自動調整する(見た目が良くなる)
  numbers=left,                         % 行番号を左側に表示
  xrightmargin=0zw,                     % 右マージンのサイズ.
  xleftmargin=1.6zw,                    % 左マージンのサイズ.行番号が2桁でも行左端からはみ出ない値.
  numberstyle={\scriptsize},            % 行番号のスタイル.スクリプトサイズのフォントを使用
  stepnumber=1,                         % 何行ごとに行番号を表示するか
  numbersep=1zw,                        % 行番号とソースコードの間の距離
  lineskip=-1.4ex,                      % ソースコードの行間(結構詰め気味)
}

環境の使い方は以下の通りです.

\begin{sourcecode}[h] % Figure環境と同様にhtbが使えます.pは使えません.
\caption{Hello World!}\label{code:hello_world}
\begin{lstlisting}
#include <stdio.h>
int main(){
    printf("Hello World!\n");
    return 0;
}
\end{lstlisting}
\end{sourcecode}

対象読者

日本ソフトウェア科学会の論文誌および大会,FOSE(ソフトウェア工学の基礎ワークショップ)などに論文を投稿する予定の人.

前提

対象スタイルファイル

日本ソフトウェア科学会 から入手可能なスタイルファイルの2017-11-09版とそれの派生であるFOSE2023年のスタイルファイルを対象としています.

対象パッケージ

ソースコードの表示をLaTeXで行うためのパッケージは色々とあると思いますが,この記事ではlistingsパッケージを対象としています.同様の症状であれば多分同じ要領で解決可能だと思いますが保証できません.

記事公開の経緯

FOSE2023で出版委員長をしていたところ,カメラレディ原稿で同様の問題に陥っている人がいたので未来の出版委員長が楽を出来るために情報を残しておきます.

問題点

以下の様なTeXファイルをコンパイルするとキャプションの位置がおかしくなります.

\documentclass{compsoft}
\usepackage{listings}

\lstset{
  basicstyle={\ttfamily},               % 基本:等幅フォント
  identifierstyle={\small},             % 識別子
  commentstyle={\smallitshape},         % コメント:斜体
  keywordstyle={\small\bfseries},       % キーワード:太字
  stringstyle={\small\ttfamily},        % 文字列
  frame={tb},                           % 上部と下部に線を表示
  breaklines=true,                      % 行が長い場合に折り返す
  columns=[l]{fullflexible},            % 列幅を自動調整する(見た目が良くなる)
  numbers=left,                         % 行番号を左側に表示
  xrightmargin=0zw,                     % 右マージンのサイズ.
  xleftmargin=1.6zw,                    % 左マージンのサイズ.行番号が2桁でも行左端からはみ出ない値.
  numberstyle={\scriptsize},            % 行番号のスタイル.スクリプトサイズのフォントを使用
  stepnumber=1,                         % 何行ごとに行番号を表示するか
  numbersep=1zw,                        % 行番号とソースコードの間の距離
  lineskip=-1.4ex,                      % ソースコードの行間(結構詰め気味)
}

% 中略
\begin{document}
% 中略
\begin{lstlisting}[caption={Hello World!},label=code:hello_world]
#include <stdio.h>
int main(){
    printf("Hello World!\n");
    return 0;
}
\end{lstlisting}
% 中略
\end{document}

こんな感じでキャプション中のgが線に跨いでしまって良くないです.lstsetの設定は割と適当でこれが最適かは自信ないですがこの設定が悪さをしているということではないです.また,画像中の上下に表示されている文章は『我が輩は猫である』の一部です.bxjalipsumというパッケージを使って出力しています.

以降,ダメな例も含めて解決方法を説明していきます.てっとり早く解決策だけ知りたい方は記事のトップに書いたTL;DRを読んで下さい.

キャプションの「Listing」を「ソースコード」に変更する

話が変わりますが,キャプションの「Listing」部分を「ソースコード」変更します.ここの変更は簡単で以下のコードをlistingsパッケージを読み込んだ後のプリアンブル(\begin{document}より前)に書けば大丈夫です.

\renewcommand{\lstlistingname}{ソースコード}

このコードを追加すると以下の様な出力になります.まだ上側の水平線とキャプションが重なってって良くないです.

(NG)belowcaptionskipを使用する

この節は記録と周知のために書いてるだけなので解決策だけ気になる人は読み飛ばしても構いません.

IEEEのフォーマットでも同様の問題が起きるようでStack Exchangeで質問されています.
https://tex.stackexchange.com/questions/469866/space-between-caption-and-listing
ここでのやりとりによると,captionパッケージと合わせてbelowcaptionskipをlstsetに追加するとキャプション直下のスペースを調節できるようです.しかし,captionパッケージを使用するようにすると,そもそものキャプションの設定が破壊されてしまいます.具体的には以下のようになります.

なお,キャプション直下のスペースは露骨に10exに設定しています.これではいけないので以下の記事を参考にcaptionの設定を保存してみましょう[1]
https://qiita.com/corda_glun/items/009095306a61009ff188

\usepackage{caption} を以下のように囲んでやります.

\makeatletter
\let\MYcaption\@makecaption
\makeatother

\usepackage{caption}

\makeatletter
\let\@makecaption\MYcaption
\makeatother

完全に振り出しに戻ってしまいました.captionパッケージによる書式の上書きを回避すると今度はbelowcaptionskipが効かなくなってしまいます.

(解決策)lstlistingをFigure環境で囲む

とりあえず囲んでみる

結局,どう頑張っても元の書式のままlstlistingが表示するキャプションの位置を調整しようとしても無理だったのでFigure環境で囲んで解決することにしました.

とりあえず,Figure環境で囲んでキャプションを表示してみます.

\begin{figure}[h]
\centering
\caption{Hello World!}\label{code:hello_world}
\begin{lstlisting}
#include <stdio.h>
int main(){
    printf("Hello World!\n");
    return 0;
}
\end{lstlisting}
\end{figure}

とりあえずキャプションの位置は適切になりました.とはいえ以下の問題があります.

  • キャプションが「ソースコード」から「図」になっている.
  • ソースコードが終わった後の本文とのスペースが開きすぎている

「図」を「ソースコード」に変更するにはcaptionsetup命令が使えます.しかし,これはcaptionパッケージが必要なので一つ前のセクションで説明したようにきちんと元の書式を保存してやる必要があります.

`\usepackage{caption}` を以下のように囲んでやります.
```tex
\makeatletter
\let\MYcaption\@makecaption
\makeatother

\usepackage{caption}

\makeatletter
\let\@makecaption\MYcaption
\makeatother

中略

\begin{figure}[h]
\centering
\captionsetup{name=ソースコード}
\caption{Hello World!}\label{code:hello_world}
\begin{lstlisting}
#include <stdio.h>
int main(){
    printf("Hello World!\n");
    return 0;
}
\end{lstlisting}
\end{figure}

中略

キャプション番号用のカウンタを分離する

これで後はソースコード前後のスペースを調節すれば終わりと思いきや実はもうひとつ大きな問題があります.この解決方法では図とソースコードで図番号を共有していますので,上のソースコードの後に図を挿入すると図2になってしまいます.

そこで,Figure環境用のカウンタを切り替えるためのマクロを用意してやります.

% ソースコードで図番号を切り替えるためにカウンタを定義
\newcounter{sourcecodefigure}
\newcounter{normalfigure}

% カウンタの切り替えマクロ
\newcommand{\switchtosourcecode}{%
    \setcounter{normalfigure}{\value{figure}}
    \setcounter{figure}{\value{sourcecodefigure}}
}

\newcommand{\switchtonormal}{%
    \setcounter{sourcecodefigure}{\value{figure}}
    \setcounter{figure}{\value{normalfigure}}
}

面倒ですがソースコードのキャプションを表示する前に \switchtosourcecode を,キャプションの表示後に \switchtonormal を呼んでやると適切にカウンタが切り替わって番号が正しくなります.

空白

最後に空白位置を調節します.適切かは自信ないですがvspaceを使って無理矢理調整します.

\begin{figure}[h]
\centering
\captionsetup{name=ソースコード}
\switchtosourcecode
\vspace{-2zh} % キャプションより前のスペース
\caption{Hello World!}\label{code:hello_world}
\begin{lstlisting}
#include <stdio.h>
int main(){
    printf("Hello World!\n");
    return 0;
}
\end{lstlisting}
\vspace{-3zh} % ソースコードの下端より後のスペース
\switchtonormal
\end{figure}

良い感じになりました.しかし,これは万能ではなく,Figure環境の位置をh(here)からt(top)やb(bottom)に変更すると再調整が必要です.tとbだとそれぞれこんな感じでしょうか[2]

% t
\vspace{-1.3zh} % 上端のスペース
\vspace{-2zh}   % 下端のスペース

% b
\vspace{-2zh}   % 上端のスペース
\vspace{-1zh}   % 下端のスペース

図やソースコードをもっと多用するともしかしたらこの値そのままではレイアウトが崩れるかもしれません.

新しい環境を作って汎用性を高める

毎回カウンタ変数を切り替えたり,配置場所によって位置調整用の数値を変更するのはとても面倒なので環境を作って楽をしましょう.だいたいはChatGPTが作ってくれた以下の環境を用意してやります.

\usepackage{ifthen}

\newcommand\sourcecodeposition{h}
\newenvironment{sourcecode}[1][h]{%
    \begin{figure}[#1]
    \renewcommand\sourcecodeposition{#1}
    \centering
    \captionsetup{name=ソースコード}
    \switchtosourcecode
    \ifthenelse{\equal{\sourcecodeposition}{t}}%
        {\vspace{-1.3zh}} % for 't'
        {\ifthenelse{\equal{\sourcecodeposition}{b}}%
            {\vspace{-2zh}} % for 'b'
            {\vspace{-2zh}} % for 'h' or default
    }
}{%
    \ifthenelse{\equal{\sourcecodeposition}{t}}%
        {\vspace{-1.3zh}} % for 't'
        {\ifthenelse{\equal{\sourcecodeposition}{b}}%
            {\vspace{-1zh}} % for 'b'
            {\vspace{-3zh}} % for 'h' or default
    }
    \switchtonormal
    \end{figure}
}

この環境は以下のように使用できます.

\begin{sourcecode}[h]
\caption{Hello World!}\label{code:hello_world}
\begin{lstlisting}
#include <stdio.h>
int main(){
    printf("Hello World!\n");
    return 0;
}
\end{lstlisting}
\end{sourcecode}

lstlisting環境の開始と終了もsourcecode環境にいれることは可能ですが,ソースコードをファイルから読み込む場合に書き方が変わるのであえて含めないようにしました.頑張ればキャプションやラベルを引数として渡すこともできるかもしれませんが面倒だしFigure環境と挙動を合わせた方が使いやすいだろうと思ってやっていません.

脚注
  1. 記事ではsubcaptionパッケージの対策として紹介されています.図にsubcaption使おうとして気軽にusepackageするとキャプションの書式が上書きされるので気をつけてください. ↩︎

  2. p(page)は面倒なので考えないことにしてます. ↩︎

Discussion