문자열 변형

graphicx는 그림을 넣는 패키지로 여겨지기 쉽지만, 이름의 x는 “확장”이지 “그림 전용”이 아닙니다. 실제로 이 패키지의 \rotatebox, \scalebox, \resizebox, \reflectbox상자에 들어가는 것이라면 무엇이든——한 단어든, 수식이든, tabular로 짠 표든——돌리고 늘이고 거울에 비춥니다. 다만 상자의 치수는 직관대로 움직이지 않습니다. 실측하면 10pt의 Xg\rotatebox{-90}으로 오른쪽으로 눕힌 상자는 높이 0pt, 깊이 12.50pt——내용 전체가 베이스라인 아래에 매달립니다. 표의 머리글을 세우는 순간 행간이 무너지는 이유가 바로 이것입니다. 이 페이지에서는 LaTeX에서 글자를 회전・확대축소하는 명령을, 치수와 드라이버라는 두 가지 함정과 함께 정리합니다.

그림 전용이 아닙니다 — graphicx의 네 가지 상자 변형

필요한 것은 \usepackage{graphicx} 한 줄뿐이며, 이것으로 네 가지 변형 명령을 쓸 수 있습니다. 넷은 두 갈래로 나뉩니다. 배율로 지정하는 것(\scalebox, \reflectbox), 목표 치수로 지정하는 것(\resizebox), 그리고 각도로 지정하는 \rotatebox입니다. \scalebox{0.8}{…}은 “원래의 0.8배”, \resizebox{5cm}{!}{…}은 “폭을 5cm로”라는 뜻입니다. 어느 쪽을 쓸지는 손에 있는 수가 비율인가 치수인가로 정해집니다. 도표를 본문 폭에 맞추려면 후자, 글자를 조금만 키우려면 전자입니다.

명령하는 일형식
\rotatebox내용을 반시계 방향으로 회전\rotatebox[origin=c]{45}{...}. 단위는 도, 음수면 시계 방향
\scalebox배율로 확대/축소\scalebox{2}[1.5]{...}. 선택적 둘째 인수는 세로 배율
\reflectbox좌우를 반전\reflectbox{...}. 내부적으로 \scalebox{-1}[1]과 동일
\resizebox지정한 최종 치수에 맞춤\resizebox{너비}{높이}{...}. 한쪽을 !로 두면 비율 유지

\rotatebox — 단위는 도, 양수면 반시계 방향

\rotatebox{각도}{내용}은 내용을 상자에 담아 지정한 각도(도 단위)만큼 반시계 방향으로 돌립니다. 양수면 왼쪽, 음수면 오른쪽입니다. \rotatebox{90}{...}은 글자를 세우고, \rotatebox{-90}은 반대쪽으로 눕힙니다. 도 이외의 단위로 각도를 쓰고 싶다면 선택 키 units=를 쓸 수 있는데, 한 바퀴에 해당하는 수를 적는 방식입니다. units=-360이면 시계 방향의 도, units=6.283185면 반시계 방향의 라디안이 됩니다. 부호를 매번 따지기가 번거롭다면 프리앰블에서 한 번 감싸 두는 편이 편합니다.

latex
This is \rotatebox{45}{slanted text} in a line of prose.

% clockwise degrees instead of the default anticlockwise:
\rotatebox[units=-360]{90}{tipped to the right}

이제 치수 이야기입니다. 대부분이 여기서 걸려 넘어집니다. 회전한 상자의 너비・높이・깊이는 회전 후의 겉모양 외곽으로부터 다시 계산됩니다. 10pt의 Xg(너비 12.50pt, 높이 6.83pt, 깊이 1.94pt)로 재면 \rotatebox{90}의 결과는 너비 8.78pt, 높이 12.50pt, 깊이 0pt입니다. 원래의 “높이+깊이”가 새 너비가 되고, 원래 너비가 새 높이가 된 것입니다. \rotatebox{-90}은 너비는 같지만 높이 0pt, 깊이 12.50pt여서 상자 전체가 베이스라인 아래로 가라앉습니다. \rotatebox{45}는 너비 15.05pt, 높이 13.67pt로 가로세로 모두 원래보다 커집니다. 회전은 행의 높이를 바꿉니다. 이 사실을 알아 두면 회전을 넣은 직후 행간이 갑자기 벌어지는 현상에 놀라지 않게 됩니다.

회전축 origin= — 대괄호를 쓰는 순간 기본값이 바뀝니다

회전의 중심은 선택 인수 [origin=…]으로 지정합니다. 값은 l(왼쪽), r(오른쪽), t(위), b(아래), B(베이스라인), c(가운데) 가운데 하나나 둘을 조합한 것으로, origin=lb이면 왼쪽 아래, origin=c이면 상자 한가운데가 축이 됩니다. 그리고 여기에 모르면 반드시 헷갈리는 장치가 있습니다. graphicx.sty를 보면, 선택 인수를 쓰지 않았을 때 회전 중심은 (0,0), 곧 왼쪽 끝이자 베이스라인——origin=lB과 같음——이고, 반대로 키를 하나라도 쓰는 순간 기본값이 상자 중심 c로 바뀝니다. 실측에서도 \rotatebox{90}{Xg}\rotatebox[origin=lB]{90}{Xg}는 너비 8.78pt, 높이 12.50pt, 깊이 0pt로 정확히 일치했고, \rotatebox[origin=c]{45}는 같은 45도인데도 높이 9.97pt, 깊이 5.08pt로 베이스라인을 걸치는 배분이 되었습니다. “units=만 더했는데 위치가 어긋났다”는 증상은 대개 이것입니다.

origin= 외에 x=y=도 있는데, 이쪽은 축의 위치를 길이로 직접 지정합니다. \rotatebox[x=1cm,y=1cm]{90}{...}처럼 쓰면 상자의 왼쪽 아래에서 잰 (1cm, 1cm)이 회전의 중심이 됩니다. 몇 개의 키워드로는 부족한 미세 조정을 할 때 쓰는 도구입니다.

latex
% no optional argument: the pivot is the left edge on the baseline (= origin=lB)
\rotatebox{90}{upright}

% ANY key present: the default pivot becomes the box centre (origin=c)
\rotatebox[origin=c]{180}{upside down}
\rotatebox[x=1cm,y=1cm]{90}{pivot given as a length}

표 머리글을 세우기 — 폭은 줄지만 행이 가라앉습니다

\rotatebox의 가장 실용적인 쓰임새는 긴 열 이름을 세워 폭을 아끼는 것입니다. 효과는 분명합니다. \begin{tabular}{ll}Header&b\\ c&d\end{tabular}의 너비는 60.42pt였는데, Header\rotatebox{90}{...}으로 세우자 36.50pt까지 줄었습니다. 다만 대가가 있습니다. 같은 실측에서 표의 깊이가 9.50pt에서 20.73pt로 늘어났습니다. 앞 절의 치수 이야기가 그대로 작동해서, 세운 머리글은 행의 아래쪽으로 자랍니다. 그대로 두면 본문이나 다음 행과 부딪히기 시작합니다. 대책은 단순합니다. \rotatebox[origin=c]{90}{...}으로 축을 가운데로 옮기거나, \raisebox로 셀을 들어 올리거나, \rule{0pt}{높이} 같은 버팀목으로 행 높이를 확보하면 됩니다. 표 자체를 지면에 앉히는 방법——가로 페이지, sidewaystable, 너비를 선언하는 열——은 표 관련 페이지의 담당이니 그쪽을 참고하세요.

latex
\begin{tabular}{lrr}
  % rotating the pivot to the centre keeps the row from sinking
  & \rotatebox[origin=c]{90}{Measured} & \rotatebox[origin=c]{90}{Predicted} \\
  \hline
  Sample A & 12.4 & 12.1 \\
  Sample B & 9.8  & 10.0 \\
\end{tabular}

배율로 확대축소・반사 — \scalebox\reflectbox

\scalebox{배율}{내용}은 가로와 세로를 같은 비율로 확대축소하므로 형태(가로세로비)를 유지합니다. \scalebox{2}{...}는 두 배, \scalebox{0.5}{...}는 절반입니다. 가로와 세로를 따로 늘이려면 선택적 둘째 인수에 세로 배율을 넘겨 \scalebox{가로배율}[세로배율]{내용}으로 씁니다. 실측에서 \scalebox{2}는 너비・높이・깊이를 모두 두 배로 만든 반면, \scalebox{1}[2]는 너비를 12.50pt 그대로 두고 높이와 깊이만 두 배로 했습니다. 글자를 길쭉하거나 납작하게 만들 때 쓸 수 있지만, 서체의 획 굵기까지 한 방향으로 일그러지므로 본문에서는 삼가는 편이 좋습니다.

배율에는 음수도 넘길 수 있어 그 축 방향으로 반전됩니다. \scalebox{-1}[1]{...}은 가로 배율 −1, 세로 배율 1, 곧 좌우 반전(거울 글자)입니다. 자주 쓰이므로 전용 약식 명령이 있고, graphics.sty 안에서 \reflectbox바로 \Gscale@box-1[1] 그 자체로 정의되어 있습니다. 실측에서도 두 상자는 너비 12.50pt, 높이 6.83pt, 깊이 1.94pt로 완전히 일치했습니다. 즉 \reflectbox\scalebox{-1}[1]의 다른 이름일 뿐입니다. 회문 “Able was I ere I saw Elba”의 절반만 거울에 비춰 보면 이 명령의 동작이 한눈에 들어옵니다.

latex
\scalebox{2}{bigger}\quad\scalebox{0.5}{smaller}\quad\scalebox{1}[2]{stretched}

% \reflectbox is literally \scalebox{-1}[1]
Able was I \reflectbox{Able was I}

목표 치수에 맞추기 — \resizebox와 별표 붙은 \resizebox*

\resizebox{너비}{높이}{내용}은 내용을 지정한 최종 치수에 맞춥니다. 한쪽에 느낌표 !를 두면 그쪽은 다른 쪽에서 구한 배율을 그대로 쓰므로 가로세로비가 유지됩니다. \resizebox{\textwidth}{!}{...}가 가장 흔히 쓰이는 형태입니다. 치수 인수 안에서는 내용의 원래 크기를 나타내는 \width, \height, \totalheight, \depth를 쓸 수 있습니다. 실측에서도 \resizebox{2\width}{!}{Xg}\scalebox{2}{Xg}와 한 치도 다르지 않게 너비 25.00pt, 높이 13.67pt, 깊이 3.89pt가 되었습니다.

놓치기 쉬운 점은 \resizebox의 높이 인수가 “높이”이지 “높이+깊이”가 아니라는 것입니다. \resizebox{!}{1cm}{Xg}를 재면 높이는 28.46pt(=거의 1cm)이지만, 그 아래에 깊이 8.10pt가 더 매달려 상자 전체로는 1cm를 넘습니다. 총높이로 지정하고 싶다면 별표 붙은 \resizebox*를 씁니다. 같은 \resizebox*{!}{1cm}{Xg}의 실측은 높이 22.15pt, 깊이 6.30pt로 합계 28.45pt——정확히 1cm에 들어맞았습니다. “1cm로 줄였는데 행 밖으로 튀어나온다” 싶으면 이 별표의 유무를 의심하세요.

latex
% twice the original width, aspect ratio kept
\resizebox{2\width}{!}{double width}

% height only vs. height+depth
\resizebox{!}{1cm}{Tall}      % height = 1cm, depth hangs below
\resizebox*{!}{1cm}{Tall}     % height + depth = 1cm

\resizebox의 가장 흔한 쓰임새는 본문 폭을 넘는 표를 통째로 줄여 넣는 것입니다. tabular 환경째 \resizebox{\textwidth}{!}{...}로 감싸기만 하면 폭은 반드시 맞습니다. 다만 이는 양날의 검이어서, 표 안의 글자까지 함께 줄어들기 때문에 강하게 줄이면 본문보다 작은——때로는 읽기 어려운——글자가 되고 주변과의 글자 모양도 어긋납니다. 나중에 표의 글꼴 크기를 다시 지정해도 결국 상자 전체가 줄어들므로 소용이 없습니다. 표가 얼마나 줄어드는지, 그리고 너무 좁은 표를 같은 명령에 넘겼을 때 몇 배까지 부풀어 오르는지는 폭이 넓은 표 페이지에서 실측값과 함께 다룹니다. 먼저 열 너비를 조정하거나 열을 덜어 표 자체를 폭에 맞추는 것이 정석이고, \resizebox는 마지막 수단으로 보는 편이 안전합니다.

회전하지 않을 때 — 드라이버 지정의 불일치

\rotatebox은 TeX이 직접 글자를 돌려 그리는 것이 아닙니다. TeX은 “여기서부터 몇 도 돌려라”라는 지시를 출력 파일에 심을 뿐이고, 실제로 돌리는 것은 뒷단의 드라이버입니다. 그래서 드라이버 지정을 잘못하면 오류도 경고도 없이 회전만 사라집니다. 실측해 봅시다. \usepackage[dvips]{graphicx}라고 쓴 문서를 pdfLaTeX으로 조판하면 컴파일은 정상 종료되고(오류 0건), 그런데 완성된 PDF의 내용 스트림에는 변환 행렬 cm이 하나도 들어 있지 않습니다. 글자는 반듯하게 놓인 그대로입니다. 같은 문서에서 드라이버 지정을 빼면 로그에 Package graphics Info: Driver file: pdftex.def가 나오고, PDF에는 .70709 .70709 -.70709 .70709 0 0 cm——45도의 코사인과 사인——이 나타납니다.

결론은 단순합니다. 드라이버를 지정하지 마세요. pdfLaTeX, XeLaTeX, LuaLaTeX 모두 graphicx가 자동으로 판별하므로, [dvips][pdftex]를 손으로 쓰는 것은 바로 불일치를 만들어 넣는 행위입니다. DVI를 거치는 경우만 이야기가 다릅니다. latex으로 조판하면 graphicx의 기본 드라이버는 dvips가 되고, DVI 안에는 날것의 PostScript(ps: gsave currentpoint currentpoint translate 45 neg rotate …)가 쓰입니다. dvipdfmx는 이런 PostScript 특수 명령을 어느 정도 해석할 수 있어 실제로는 돌아가지만, 경로로서 깔끔한 것은 \usepackage[dvipdfmx]{graphicx}를 명시하는 쪽이고, 그때 DVI에는 PDF용 지시인 pdf:btrans rotate 45가 들어갑니다. DVI가 드라이버에 무엇을 넘기는지에 대한 이야기 자체는 DVI와 PDF 변환을 다루는 페이지에 자세히 있습니다.

더 높은 수준의 adjustbox — max width=는 “줄이기만”

graphicx 명령은 단순하고 강력하지만 조합하면 중첩이 깊어집니다. \usepackage{adjustbox}로 읽어들이는 adjustbox는 이들을 \includegraphics키=값 인터페이스로 통합한 더 높은 수준의 대안입니다. \adjustbox{angle=45,scale=0.8}{…}처럼 회전(angle=), 배율(scale=), 목표 치수(width=/height=), 반사(reflect)를 한 번에 지정할 수 있고, 같은 이름의 adjustbox 환경도 있습니다. 실측에서 \adjustbox{angle=45}{Xg}의 상자가 \rotatebox{45}{Xg}와 한 치도 다르지 않았으므로 내부에서 하는 일은 같습니다. 차이는 쓰기 편함과, 다음에 이야기할 “상한”이라는 발상에 있습니다.

특히 편리한 것은 max width=\textwidth(및 max totalheight= 등)입니다. 앞 절의 \resizebox{\textwidth}{!}{...}는 내용이 본문 폭보다 좁아도 확대해 버리지만, max width=는 “넘칠 때만 줄이고, 이미 들어가면 손대지 않도록” 만들어져 있습니다. 실측에서도 한 열짜리 작은 tabular(너비 17.00pt)는 max width=\textwidth를 통과해도 17.00pt 그대로였지만, 같은 표를 \resizebox{\textwidth}{!}에 넘기자 \textwidth 가득한 345.00pt까지 늘어났습니다. 그러니 \includegraphics 버릇으로 모든 표에 \resizebox를 붙이면 좁은 표만 괴물이 됩니다. “필요할 때만 줄이고 결코 확대하지 않는다”를 기본으로 삼는 것 ——실무에서는 이것이 사고가 가장 적은 선택입니다.

latex
\usepackage{adjustbox}
% ...
\adjustbox{angle=45,scale=0.8}{rotate and scale in one go}

% shrinks only when the content overflows; leaves narrow tables alone
\adjustbox{max width=\textwidth}{%
  \begin{tabular}{lrr} ... \end{tabular}%
}