☃️

\tcbset のスコープ(あるいはどうすれば tcolorbox の内部で局所化できるか)

2024/06/21に公開

tcolorbox に対する大域的な設定には \tcbset を使う。ここで「大域的」というのは、厳密には「TeXのグループ」を指す。なので、\begingroup...\endgroup で囲めば局所的な変更に使える。

しかし、「tcolorboxの中でさらに内側のtcolorboxを設定する」という使い方はできない。それどころか、外側の \tcbset も無視される。

\tcbset{colbacktitle=red}
\begin{tcolorbox}[title=タイトルの背景は赤]
  \tcbset{coltitle=white, colbacktitle=blue}
  \begin{tcolorbox}[title=内側はタイトルの背景が青になってほしいな]
    本文
  \end{tcolorbox}
\end{tcolorbox}

入れ子の \tcbset は効いてないし、外側の \tcbset すら効いてない。つまり、tcolorbox は「TeXのグループ」を作るわけではないことがわかる。(ほんと?)

実は、入れ子の tcolorbox は「レイヤ」という概念で制御されている。レイヤについて詳しくは tcolorbox のマニュアル(Ver.6.2.0版では「4.16 Layered Boxes and Every Box Settings」)を参照。

tcolorbox の中でも \tcbset を効かせるためには、全レイヤに適用させるバージョンの \tcbsetforeverylayer というのがあるので、そっちを使う。

\tcbset{colbacktitle=red}
\begin{tcolorbox}[title=タイトルの背景は赤]
  \tcbsetforeverylayer{coltitle=white, colbacktitle=blue}
  \begin{tcolorbox}[title=内側はタイトルの背景が青になる]
    本文
  \end{tcolorbox}
\end{tcolorbox}

これでめでたしめでたしならいいんだけど、まだ罠がある。それは tcolorbox のタイトルを「Boxed Title」で作っている場合。Boxed Title 自体がレイヤなので、たとえばこんなふうにすると、すべてのレイヤにtitleを適用しようとして無限ループが起きてしまう。

\tcbset{colbacktitle=red}
\begin{tcolorbox}[title=タイトルの背景は赤]
  \tcbsetforeverylayer{
    enhanced, attach boxed title to top,
    title=Boxed Titleという仕組みで付けたタイトル
  }
  \begin{tcolorbox}
    本文
  \end{tcolorbox}
\end{tcolorbox}

title 属性を局所的に記述するようにすれば実行は終了して結果が得られる。

\tcbset{colbacktitle=red}
\begin{tcolorbox}[title=タイトルの背景は赤]
  \begingroup
  \tcbsetforeverylayer{enhanced, attach boxed title to top}
  \begin{tcolorbox}[title=Boxed Titleという仕組みで付けたタイトル]
    本文
  \end{tcolorbox}
  \endgroup
\end{tcolorbox}

しかし、実は次のような書き方をしても通る。どうやら title とBoxed Titleの設定を同時に適用しようとすると無限ループになるらしい。

\tcbset{colbacktitle=red}
\begin{tcolorbox}[title=タイトルの背景は赤]
  \tcbsetforeverylayer{title=Boxed Titleという仕組みで付けたタイトル}
  \begin{tcolorbox}[enhanced, attach boxed title to top]
    本文
  \end{tcolorbox}
\end{tcolorbox}

ところが、これは通りはするのだけど、出力結果はびっくりするものになる。

おそらく、Boxed Title自体がまたレイヤにより実現していて、\tcbsetforeverylayer で設定した title が再帰的に適用されてしまうのだろう。

では、\tcbset\tcbsetforeverylayer を使って(つまりtcolorboxへの引数としてではなく)、Boxed Titleにタイトル文字列を設定することはできないのだろうか?

実はできる。\tcbset には適用されるレイヤを細かく指定する機能があり、それを利用すれば、再帰的に適用されてタイトルが重複することなく、Boxed Titleのタイトル文字列を \tcbset で設定できる。

\tcbset{colbacktitle=red}
\begin{tcolorbox}[title=タイトルの背景は赤]
  \tcbset{every box on layer 2/.style={title=Boxed Titleという仕組みで付けたタイトル}}
  \begin{tcolorbox}[enhanced, attach boxed title to top]
    本文
  \end{tcolorbox}
\end{tcolorbox}

every box on layer 2 という指定により、この設定を2つめのレイヤのみに制限している。「Boxed Titleの仕組みでタイトルの見た目を作りこんだtcolorboxを動的に生成するLaTeXコマンド」のようなものが必要で、しかもそれを別のtcolorboxの中でも使えるようにしたいという場合は、この仕掛けを使う必要があるだろう。

Discussion