Eigene Umgebungen (\newenvironment)

Wenn Sie im Dokument immer wieder einen Bereich mit derselben Formatierung umschließen, können Sie ein eigenes \begin{name}…\end{name} bauen. Die Grundlage ist \newenvironment. Ist ein Makro ein selbstgebauter *Befehl*, dann ist eine Umgebung sein Block-Gegenstück. Diese Seite behandelt die Basis: den getrennten Code am Anfang und am Ende, die Übergabe von Argumenten samt Fallstrick, das Neudefinieren bestehender Umgebungen und das moderne \NewDocumentEnvironment.

Eine Umgebung mit \newenvironment definieren

Das Grundwerkzeug ist \newenvironment{name}{⟨begin-def⟩}{⟨end-def⟩}. Das erste Argument ist der Name der gewünschten Umgebung, ohne Backslash; die nächsten beiden enthalten den Code für den Anfang und den Code für das Ende. Bei \begin{name} führt LaTeX ⟨begin-def⟩ aus, bei \end{name} ⟨end-def⟩; der eingeschlossene Text wird ganz normal gesetzt.

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

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

Eine entscheidende Eigenschaft lautet: Eine Umgebung bildet selbst eine implizite Gruppe, also einen lokalen Gültigkeitsbereich. Die LaTeX-Regeln legen fest, dass der Code von ⟨begin-def⟩, der Umgebungstext und der Code von ⟨end-def⟩ gemeinsam in einer Gruppe verarbeitet werden. Änderungen an Schrift oder Abstand in ⟨begin-def⟩ werden daher bei \end{name} automatisch rückgängig gemacht und laufen nicht in den umgebenden Text aus. Darum ist im Beispiel nur der Körper kursiv, obwohl \itshape nicht explizit zurückgesetzt wird.

Diese automatische Gruppierung ist sehr praktisch, wenn eine Schrift- oder Abstandsänderung auf einen festen Bereich begrenzt bleiben soll. Mit einem Makro (\newcommand) müssten Sie den Inhalt selbst in { … } einschließen, um eine Gruppe zu bilden; bei einer Umgebung übernehmen \begin\end diese Aufgabe.

Umgebungen mit Argumenten — und warum der Endcode sie nicht sieht

Soll der Inhalt pro Aufruf variieren, geben Sie der Umgebung Argumente. Genau wie bei \newcommand schreiben Sie die Anzahl der Argumente in eckige Klammern nach dem Namen und greifen in ⟨begin-def⟩ mit #1, #2 usw. darauf zu: \newenvironment{name}[⟨nargs⟩]{⟨begin-def⟩}{⟨end-def⟩} (von #1 bis #9, höchstens neun).

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

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

Wie bei \newcommand können Sie außerdem das erste Argument optional machen und ihm einen Standardwert geben. Doppelte Klammern — \newenvironment{name}[⟨nargs⟩][⟨default⟩]{...}{...} — machen #1 zu einem optionalen Argument mit Standard ⟨default⟩. Aufgerufen wird mit \begin{name} (Standardwert) oder \begin{name}[x] (#1 wird x). Auch hier ist ⟨nargs⟩ in [⟨nargs⟩] die Gesamtzahl der Argumente einschließlich des optionalen.

Hier liegt der größte, umgebungsspezifische Fallstrick. Die Argumente #1, #2, … dürfen nur in ⟨begin-def⟩ verwendet werden; in ⟨end-def⟩ stehen sie nicht zur Verfügung. Die Regeln sagen ausdrücklich, dass der Endcode keine Parameter enthalten darf, also dort kein #1, #2 usw. möglich ist. Kurz gesagt: Wenn \end{name} läuft, sind die Argumente nicht mehr greifbar. Ein #1 in ⟨end-def⟩ expandiert nicht zum Argument, sondern führt zu einem Fehler.

Benötigen Sie den Argumentwert am Ende doch, speichern Sie ihn noch während ⟨begin-def⟩. Text kann in einer Box abgelegt werden (mit \newsavebox reservieren und mit \sbox füllen), oder ein Makro merkt ihn sich über \newcommand/\def; in ⟨end-def⟩ wird er dann abgerufen. Weil die ganze Umgebung eine Gruppe ist, bleibt das in ⟨begin-def⟩ Gespeicherte bis ⟨end-def⟩ erhalten. Das folgende Beispiel gibt am Ende eines Zitats eine Quelle aus: #1 nimmt die Quelle auf (Standard Shakespeare), speichert sie in \quoteauthor und nutzt sie beim Schließen.

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 und bereits vorhandene Namen

Umgebungen haben denselben Schutz wie \newcommand: \newenvironment gelingt nur, wenn der Name noch nicht definiert ist. Wird ein vorhandener Umgebungsname verwendet, stoppt LaTeX mit LaTeX Error: Command \name already defined. (intern stützt sich die Umgebung name auf einen Befehl \name, daher die Formulierung). Das ist Absicht, damit vorhandene Umgebungen nicht stillschweigend überschrieben werden.

Um eine vorhandene Umgebung umzubauen, verwenden Sie \renewenvironment. Die Argumente werden genau wie bei \newenvironment geschrieben, einschließlich [⟨nargs⟩][⟨default⟩]; nur das Verhalten ist anders. \renewenvironment meldet einen Fehler, wenn das Ziel noch nicht definiert ist, also das Spiegelbild von \newenvironment. Damit ersetzt man etwa den Inhalt eingebauter Umgebungen wie quote oder itemize.

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

Ein paar Regeln gelten. Erstens darf ein Umgebungsname nicht mit der Zeichenkette end beginnen, weil die Verarbeitung von \end{...} intern einen Befehl \end<name> benutzt und so Kollisionen vermieden werden. Zweitens besitzen \newenvironment und \renewenvironment jeweils eine Sternform mit * nach dem Namen; sie unterscheidet sich in der Behandlung von Leerraum am Argumentende. Meist reicht die Form ohne Stern. Ein Standard-Gegenstück zu \providecommand, also „nur definieren, falls nicht vorhanden“, gibt es für Umgebungen nicht; dafür nutzt man die moderne Schnittstelle im nächsten Abschnitt.

Gängige Muster

In der Praxis fallen eigene Umgebungen meist in drei Muster. Alle folgen demselben Gerüst: in ⟨begin-def⟩ vorbereiten, in ⟨end-def⟩ aufräumen.

  • In Formatierung einschließen — in ⟨begin-def⟩ Schrift, Größe oder Ausrichtung setzen und den Körper so übernehmen lassen. Dank Gruppierung kann ⟨end-def⟩ leer sein, die Änderung wird trotzdem automatisch zurückgenommen.
  • Abstand davor und danach einfügen — vertikalen Abstand wie \par\medskip am Anfang von ⟨begin-def⟩ und am Ende von ⟨end-def⟩ setzen, sodass der Körper oben und unten eingerahmt wird.
  • Auf einer bestehenden Umgebung aufbauen — in ⟨begin-def⟩ eine andere Umgebung mit \begin{...} öffnen und in ⟨end-def⟩ das passende \end{...} schließen. So würzt man etwa quote, center oder list mit etwas Zusatzverhalten.

Das dritte Muster, auf einer vorhandenen Umgebung aufzubauen, kommt am häufigsten vor. Eine Umgebung smallquote, die quotation wiederverwendet, aber kleiner setzt, schaltet in ⟨begin-def⟩ einfach \small ein und öffnet \begin{quotation}, dann schließt ⟨end-def⟩ mit \end{quotation}. Das Verhalten der zugrunde liegenden Umgebung, also Einzug und Ränder, bleibt erhalten.

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}
}

Achten Sie darauf, alles, was in ⟨begin-def⟩ geöffnet wurde, in ⟨end-def⟩ wieder zu schließen. Außerdem hilft es, Codezeilen mit % zu beenden, damit ungewollter Leerraum am Ende von ⟨begin-def⟩ oder einer Codezeile nicht in die Ausgabe gelangt. Darum enden die Zeilen in den mehrzeiligen Beispielen oben mit %.

Das moderne \NewDocumentEnvironment

Modernes LaTeX bietet eine weitere, mächtigere Form: \NewDocumentEnvironment{name}{⟨arg-spec⟩}{⟨begin-code⟩}{⟨end-code⟩}. Sie entspricht auf der Makroseite \NewDocumentCommand, war ursprünglich Teil des Pakets xparse, ist aber seit Oktober 2020 Teil des LaTeX-Kernels und ohne \usepackage verfügbar. Statt einer Argumentzahl wird eine Argument-Spezifikation (arg-spec) deklariert: Buchstaben wie m (pflicht), o (optional), O{default} (optional mit Standard) und s (optionaler Stern). Die Details stehen auf der xparse-Seite.

Diese Schnittstelle ist dem alten \newenvironment in zwei Punkten überlegen. Erstens kann sie mehrere optionale Argumente und Sternvarianten sauber behandeln. Zweitens, und entscheidend: Die Argumente sind in ⟨end-code⟩ ebenso verfügbar wie in ⟨begin-code⟩. Die Kernel-Dokumentation formuliert es ausdrücklich: Sowohl ⟨begin-code⟩ als auch ⟨end-code⟩ dürfen auf die durch ⟨arg-spec⟩ definierten Argumente zugreifen. Der Umweg über eine gespeicherte Box entfällt.

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}

Zu \NewDocumentEnvironment gehört auch die Familie, die den Geschwistern von \newenvironment entspricht: \RenewDocumentEnvironment (neu definieren), \ProvideDocumentEnvironment (nur definieren, falls nicht vorhanden) und \DeclareDocumentEnvironment (unabhängig vom Bestand definieren). Für neuen Code ist diese moderne Schnittstelle empfehlenswert, wegen der flexibleren Argumente und weil der Endcode die Argumente sehen kann.