사용자 정의 환경(\newenvironment)

문서 안에서 같은 서식으로 둘러싼 영역을 반복해서 써야 한다면, 자신만의 \begin{name}…\end{name}을 만들 수 있습니다. 기본 도구는 \newenvironment입니다. 매크로가 직접 만든 “명령”이라면, 환경은 그 블록판 입니다. 이 페이지에서는 환경을 정의하는 기초, 즉 시작할 때와 끝날 때 실행되는 코드, 인수를 넘기는 법과 그 함정, 기존 환경을 다시 정의하는 법, 현대적인 \NewDocumentEnvironment까지 차례로 살펴봅니다.

\newenvironment로 환경 정의하기

기본 도구는 \newenvironment{name}{⟨begin-def⟩}{⟨end-def⟩} 입니다. 첫 번째 인수는 만들 환경의 이름(백슬래시는 붙이지 않음)이고, 다음 두 인수에는 각각 시작할 때 실행할 코드끝날 때 실행할 코드 를 씁니다. LaTeX가 \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⟩]{...}{...} 형태를 쓰면 #1이 선택 인수가 되고 기본값은 ⟨default⟩가 됩니다. 호출은 \begin{name}(기본값 사용) 또는 \begin{name}[x](#1x로 설정)입니다. \newcommand와 같이 [⟨nargs⟩]⟨nargs⟩에는 선택 인수를 포함한 전체 인수 수 를 씁니다.

여기에는 환경 고유의, 가장 큰 함정이 있습니다. 인수 #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<name>이라는 명령을 사용하므로 충돌을 피하기 위한 제약입니다. 둘째, \newenvironment\renewenvironment에는 이름 뒤에 *를 붙인 별표형 이 있으며, 인수 끝 공백을 다루는 방식이 다릅니다. 보통은 별표 없는 형태로 충분합니다. \providecommand에 해당하는 “없을 때만 정의” 환경판은 표준에는 없으므로, 필요하면 다음 절의 현대적 인터페이스를 씁니다.

자주 쓰는 구성 방식

실무에서 환경을 직접 만드는 경우는 대체로 세 가지 패턴으로 모입니다. 모두 ⟨begin-def⟩에서 준비하고 ⟨end-def⟩에서 마무리한다는 같은 뼈대를 공유합니다.

  • 서식으로 감싸기⟨begin-def⟩에서 글꼴, 크기, 정렬을 설정하고 본문이 그 서식으로 조판되게 합니다. 그룹성 덕분에 ⟨end-def⟩가 비어 있어도 변경은 자동으로 되돌아갑니다.
  • 앞뒤에 간격 넣기⟨begin-def⟩ 앞쪽과 ⟨end-def⟩ 뒤쪽에 \par\medskip 같은 세로 간격을 두어 본문을 위아래 여백으로 감쌉니다.
  • 기존 환경 위에 만들기⟨begin-def⟩에서 다른 환경을 \begin{...}로 열고, ⟨end-def⟩에서 대응하는 \end{...}로 닫습니다. quote, center, list를 바탕으로 조금 덧붙이는 방식입니다.

세 번째 “기존 환경 위에 만들기”가 가장 자주 쓰입니다. 예를 들어 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{default}(기본값 있는 선택), s(별표 유무) 같은 인수 지정(arg-spec) 문자열로 선언합니다. 지정자 상세는 “xparse” 페이지에서 다룹니다.

이 인터페이스가 오래된 \newenvironment보다 나은 점은 두 가지입니다. 하나는 여러 선택 인수와 별표형 변종을 정식으로 다룰 수 있다 는 점입니다. 다른 하나가 결정적인데, 인수를 ⟨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(기존 여부와 관계없이 정의) 도 갖춰져 있습니다. 새 코드에서는 인수 처리의 자유도와 “끝 코드에서도 인수를 쓸 수 있음”이라는 장점 때문에 이 현대적 인터페이스를 권장합니다.