環境の自作(\newenvironment)

同じ書式で囲んだ領域を文書中で繰り返すなら、自分専用の \begin{名前}…\end{名前} を作れます。基本は \newenvironment。マクロが「命令」の自作なら、環境はその ブロック版 です。開始時・終了時に走るコードの書き分け、引数の渡し方とその落とし穴、既存環境の作り直し、そして現代的な \NewDocumentEnvironment まで、環境を自作する土台をひととおり見ていきます。

\newenvironment で環境を作る

基本の道具が **\newenvironment{name}{⟨begin-def⟩}{⟨end-def⟩} です。第 1 引数に作りたい環境の名前(バックスラッシュは付けません)、続く 2 つの引数に 開始時のコード終了時のコード** を書きます。\begin{name} に出会うと ⟨begin-def⟩ が、\end{name} に出会うと ⟨end-def⟩ が実行され、その間に挟まれた本文がふつうに組まれます。

latex
% プリアンブルで定義 / define in the preamble
\newenvironment{warning}{%
  \par\noindent\textbf{注意:}\itshape
}{%
  \par
}

% 本文で使う / use it in the body
\begin{warning}
  この操作は元に戻せません。
\end{warning}

決定的に大事な性質として、環境はそれ自体が暗黙のグループ(局所的な範囲)を作ります。LaTeX の規定では「⟨begin-def⟩ のコード・本文・⟨end-def⟩ のコードは、すべて一つのグループの中で処理される」と定められています。したがって ⟨begin-def⟩ の中で行った書体やスペーシングの変更は、\end{name} を抜けると自動的に元へ戻り、外の本文には漏れません。上の例で \itshape(斜体)を明示的に戻していないのに本文だけ斜体になるのは、このためです。

この「自動でグループになる」性質は、書体や余白の変更を一定範囲に閉じ込めたいときに大変便利です。マクロ(\newcommand)で同じことをすると、中身を { … } で自分でくくってグループを作る必要がありますが、環境では \begin\end がその役目を兼ねます。

引数をとる環境と「終了コードは引数を見られない」

中身を呼び出しごとに変えたいなら、引数を持たせます。\newcommand とまったく同じ書き方で、名前のあとの角括弧に 引数の個数 を書き、⟨begin-def⟩ の中で **#1#2 … と参照します。\newenvironment{name}[⟨nargs⟩]{⟨begin-def⟩}{⟨end-def⟩}** の形です(#1 から #9 まで、最大 9 個)。

latex
% 見出しの語を引数で受け取る / take the heading word as an argument
\newenvironment{point}[1]{%
  \par\noindent\textbf{#1}\quad
}{%
  \par
}

\begin{point}{結論}
  早めにバックアップを取ること。
\end{point}

さらに、\newcommand と同じく 最初の引数を「省略可能」にして既定値を与える こともできます。**\newenvironment{name}[⟨nargs⟩][⟨default⟩]{...}{...}** のように角括弧を 2 つ重ねると、#1 がオプション引数になり、その既定値が ⟨default⟩ になります。呼び出しは \begin{name}(既定値を使う)または \begin{name}[x]#1x にする)。[⟨nargs⟩] には オプション引数も含めた総数 を書く点も \newcommand と共通です。

ここで環境特有の、そして最大の落とし穴があります。**引数 #1#2 … は ⟨begin-def⟩ でしか使えず、⟨end-def⟩ の中では使えません**。規定にも「終了コードはパラメータを含んではならない、つまり #1#2 などをここで使うことはできない」と明記されています。理由を一言でいえば、\end{name} の時点ではもう引数が手元に残っていないからです。⟨end-def⟩ の中で #1 と書くと、引数が展開されるのではなくエラーになります。

終了時にも引数の値が必要なら、定石は **⟨begin-def⟩ のうちにその値を保存しておく ことです。文字列なら ボックス(\newsavebox で確保し \sbox に詰める)** に、あるいはマクロに \newcommand\def で覚えさせ、⟨end-def⟩ ではそれを取り出します。環境全体が一つのグループなので、⟨begin-def⟩ で保存した内容は ⟨end-def⟩ まで生き残ります。次は引用の最後に出典を出す例で、出典の語を #1(既定 Shakespeare)で受け取り、ボックス \quoteauthor に保存して終了時に使っています。

latex
\newsavebox{\quoteauthor}
\newenvironment{citequote}[1][Shakespeare]{%
  \sbox\quoteauthor{#1}%   begin-def で引数を保存 / save the arg here
  \begin{quotation}
}{%
  \hspace{1em plus 1fill}---\usebox{\quoteauthor}%   end-def で取り出す
  \end{quotation}%
}

\begin{citequote}
  To be, or not to be.
\end{citequote}

\begin{citequote}[Knuth]
  Premature optimization is the root of all evil.
\end{citequote}

\renewenvironment と「すでにある名前」

\newcommand と同じ安全装置が環境にもあります。**\newenvironment は、その名前がまだ定義されていないときだけ成功** します。すでに存在する環境名に使うと、LaTeX Error: Command \name already defined. というエラーで止まります(環境 name は内部的に \name というコマンドを使うため、このメッセージになります)。これは既存の環境をうっかり上書きする事故を防ぐための、わざとの仕様です。

既存の環境を作り直したいときは **\renewenvironment** を使います。引数の書き方([⟨nargs⟩][⟨default⟩] も含めて)は \newenvironment とまったく同じで、違うのは振る舞いだけ。\renewenvironment は対象が すでに定義されていないとエラー になります(\newenvironment のちょうど裏返しです)。標準の quoteitemize のような既存環境の中身を差し替えるときに使います。

latex
% 既存の quote を、本文を斜体にする版へ作り直す
\renewenvironment{quote}{%
  \list{}{\rightmargin\leftmargin}\item\relax\itshape
}{%
  \endlist
}

いくつか守るべき規則があります。第一に、**環境の名前は文字列 end で始められません**(\end{...} の処理は内部で \end<名前> という名のコマンドを使うため、衝突を避ける制約です)。第二に、\newenvironment\renewenvironment には名前の末尾に * を付けた スター付きの派生形 があり、これは引数末尾の空白の扱いが異なります(通常はスターなしで十分です)。\providecommand に相当する「無いときだけ定義する」環境版は標準では用意されておらず、必要なら次節の現代的なインタフェースを使います。

よく使う組み立て方

実務で環境を自作する場面は、おおむね次の三つに集約されます。いずれも ⟨begin-def⟩ で「下ごしらえ」をし、⟨end-def⟩ で「後始末」をする、という同じ骨格です。

  • 書式で包む⟨begin-def⟩ で書体・サイズ・寄せを設定し、本文をその書式で組む。グループ性のおかげで ⟨end-def⟩ は空でも自動的に元へ戻る。
  • 前後に空きを入れる⟨begin-def⟩ の先頭と ⟨end-def⟩ の末尾に \par\medskip などの縦アキを置き、本文を上下の余白で囲む。
  • 既存の環境の上に作る⟨begin-def⟩ で別の環境を \begin{...} し、⟨end-def⟩ で対応する \end{...} を閉じる。quotecenterlist を土台に少しだけ味付けする使い方。

三つ目の「既存の環境の上に作る」が最もよく使われます。たとえば、quotation をそのまま使いつつ小さめの文字で組む smallquote 環境は、⟨begin-def⟩\small を効かせてから \begin{quotation} を開き、⟨end-def⟩\end{quotation} を閉じるだけです。土台の環境の挙動(字下げや余白)はそのまま受け継がれます。

latex
% 1) 書式で包む / wrap in formatting
\newenvironment{aside}{\par\small\itshape}{\par}

% 2) 前後に空きを入れる / add space around
\newenvironment{spaced}{\par\medskip\noindent}{\par\medskip}

% 3) 既存の環境の上に作る / build on quotation
\newenvironment{smallquote}{%
  \small\begin{quotation}
}{%
  \end{quotation}
}

注意点として、⟨begin-def⟩ で開いた環境は必ず ⟨end-def⟩ で閉じ、対応をそろえてください。また、⟨begin-def⟩ の末尾やコード行の終わりに付く 意図しない空白 が出力に紛れ込まないよう、行末に % を置く習慣も役立ちます(上の複数行の例で各行末に % を付けているのはこのためです)。

現代的な \NewDocumentEnvironment

もう一つ、現代の LaTeX が用意する強力な定義法が **\NewDocumentEnvironment{name}{⟨arg-spec⟩}{⟨begin-code⟩}{⟨end-code⟩}** です。マクロ側の \NewDocumentCommand と対をなすもので、もとは xparse パッケージの機能でしたが、2020 年 10 月以降は LaTeX カーネルに取り込まれ\usepackage なしで使えます。引数の個数を数字で書く代わりに、m(必須)・o(省略可能)・O{既定値}(既定値つき)・s(星の有無)といった 「引数指定(arg-spec)」 の文字列で宣言します(指定子の詳細は「xparse」のページへ)。

このインタフェースが古い \newenvironment より優れている点は二つあります。一つは、オプション引数を複数とったり、星付き(starred)の変種を正式に扱える こと。もう一つが決定的で、**引数を ⟨begin-code⟩ だけでなく ⟨end-code⟩ でも使える** ことです。規定にも「⟨begin-code⟩⟨end-code⟩ のどちらも、⟨arg-spec⟩ で定義した引数にアクセスできる」と明記されています。さきほどの「保存ボックス」の手間が不要になる、というわけです。

latex
% #1 を begin でも end でも使える / #1 usable in both begin and end
\NewDocumentEnvironment{citequote}{O{Shakespeare}}{%
  \begin{quotation}
}{%
  \hspace{1em plus 1fill}---#1%
  \end{quotation}%
}

\begin{citequote}[Knuth]
  Premature optimization is the root of all evil.
\end{citequote}

\NewDocumentEnvironment にも、\newenvironment の仲間に対応する **\RenewDocumentEnvironment(再定義)・\ProvideDocumentEnvironment(無いときだけ定義)・\DeclareDocumentEnvironment(既存を問わず定義)** がそろっています。新しく書くコードでは、引数まわりの自由度と「終了コードでも引数が使える」利点から、こちらの現代的なインタフェースを使うのがおすすめです。