TeX Live 2024에 들어 있는 hyphen.tex의 첫 줄은 외침으로 시작합니다. The Plain TeX hyphenation tables [NOT TO BE CHANGED IN ANY WAY!]. LaTeX이 줄바꿈 위치를 정할 때마다 참조하는 영어 하이픈 규칙 4447개가 담긴 파일이고, 그 끝에는 커누스가 손으로 적은 열네 단어짜리 예외 목록이 붙어 있습니다. 그중 present와 project에는 끊을 자리가 하나도 없습니다. 같은 철자가 발음에 따라 다른 낱말이 되는데 TeX은 어느 쪽인지 알 길이 없으니 아예 끊지 않기로 한 것입니다. 줄 바꿈 위치를 조정한다는 것은 결국 그 판단을 자기 원고에서 다시 내리는 일입니다. 이 페이지에서는 \\, \newline, \linebreak[n], \nolinebreak[n], 임의 하이픈 \-, \hyphenation{...}, hyphenat 패키지, 그리고 babel의 언어별 하이픈 패턴까지 실측값과 함께 다룹니다.
먼저 경계를 그어 둡니다. 빈 줄이나 \par로 문단을 바꾸는 일과, 지금부터 다룰 문단 안에서 줄이 끊기는 자리를 옮기는 일은 서로 다릅니다. 앞의 것은 들여쓰기와 문단 사이 여백을 함께 데려오고, 뒤의 것은 한 문단 안에서 줄 끝의 위치만 옮깁니다. 그 바깥에는 지면의 리듬을 설계하는 층, 곧 \widowpenalty와 \raggedbottom의 세계가 따로 있으며 그쪽은 쪽 나눔 설계 페이지가 맡습니다. 문단을 한 줄 줄이거나 늘려 달라고 부탁하는 \looseness도 실측과 함께 그쪽에 있습니다. 이 페이지는 오직 줄의 층만 다룹니다.
\\와 \newline의 차이: 같은 매크로, 다른 입구
\newline은 \@normalcr\relax로 정의되어 있고, \\는 그 \relax가 빠진 같은 매크로입니다(latex.ltx). 차이는 이 토큰 하나뿐입니다. \\는 뒤따르는 *나 [길이]를 찾아 읽지만, \newline은 \relax에 막혀 읽으러 가지 못합니다. 실제로 Alpha\newline[2ex] Beta를 조판하면 지면에 [2ex]가 그대로 찍힙니다. 반대로 \\[2ex]는 그 줄 뒤에 2ex의 세로 여백을 넣고, \\*는 그 자리에서 쪽 나눔을 금지합니다. 실무에서는 옵션이 필요하면 \\, 원고에서 의도를 드러내고 싶으면 \newline을 씁니다.
둘의 공통점은 줄을 늘이지 않고 끝낸다는 점입니다. 본체는 \unskip … \nobreak \hfil \break로, 오른쪽에 무한히 늘어나는 \hfil을 놓은 다음에야 끊습니다. 이 \hfil이 남는 공간을 전부 삼키므로 줄은 자연스러운 길이에서 멈추고 짧게 남습니다. 주소나 시처럼 각 줄이 의도한 자리에서 끝나야 하는 글에 어울리는 이유가 바로 이것입니다. 다만 부작용이 하나 있습니다. 문단 맨 끝에 \\를 두면 내용 없는 마지막 줄이 생기고 Underfull \hbox (badness 10000) in paragraph at lines 6--7이 나옵니다. 문단 끝에는 \\가 필요 없습니다. 제목 바로 다음 줄도 마찬가지입니다. 끝낼 줄이 아예 없는 자리(문단 첫머리 등)에서 쓰면 이번에는 ! LaTeX Error: There's no line here to end.가 나옵니다.
% \\ and \newline end the line short; \linebreak justifies it to the margin.
First road is steep\\
second road is gentle.
% \\ takes an optional length and a star; \newline takes neither.
Alpha\\[2ex] Beta % 2ex of vertical space after the line
Alpha\\* Gamma % no page break allowed here
Alpha\newline[2ex] Delta % prints a literal [2ex] on the page
% A trailing \\ at the end of a paragraph produces
% Underfull \hbox (badness 10000) in paragraph at lines 6--7
This paragraph does not need one.표 안에서는 두 역할이 뚜렷하게 갈립니다. tabular의 p{...} 열처럼 문단을 만드는 셀에서는 \newline이 그 셀 안에서만 줄을 끊고, \\는 행 전체를 끝냅니다. 주소를 한 셀에 두 줄로 넣고 싶다면 \newline입니다. 실측 결과 p{3cm} 셀에 Line one\newline Line two를 쓰면 같은 행 안에서 두 줄로 쌓이고 오른쪽 셀은 그대로 있었습니다. array 환경에서도 \\가 행 구분자입니다. 반대로 l이나 c처럼 문단을 만들지 않는 열에서 \newline을 쓰면 끝낼 줄이 없으므로 앞서 본 오류가 납니다.
\linebreak[n]과 \nolinebreak[n]의 n은 실제로 얼마의 페널티인가
n은 0부터 4까지 다섯 단계이며 각각 0・51・151・301・10000이라는 페널티로 번역됩니다. latex.ltx의 \@getpen이 0 / \@lowpenalty / \@medpenalty / \@highpenalty / \@M을 돌려주고, 표준 클래스에서는 그 셋이 article.cls 118~120행에서 51, 151, 301로 정의되어 있기 때문입니다. \linebreak[n]은 이 값을 음수로 넣고(여기서 끊으면 이득), \nolinebreak[n]은 양수로 넣습니다(여기서 끊으면 손해). 인수를 생략하면 둘 다 4, 곧 ∓10000으로 TeX에게는 무한대입니다. 숫자가 보이면 선택이 정해집니다. [1]의 51은 본문의 badness가 쉽게 이기는 속삭임이고, [3]의 301에서야 비로소 주장이 되며, 명령은 [4]뿐입니다.
| 인수 | linebreak이 넣는 값 | nolinebreak이 넣는 값 | 실제 강도 |
|---|---|---|---|
[0] | 0 | 0 | 그저 합법적인 끊을 자리. 이득도 손해도 없음 |
[1] | -51 | 51 | 속삭임. 본문의 badness에 집니다 |
[2] | -151 | 151 | 기본 \widowpenalty와 비슷한 무게 |
[3] | -301 | 301 | 여기서야 비로소 주장이 됩니다 |
[4] | -10000 | 10000 | 기본값. 무한대, 곧 명령 |
\linebreak과 \\는 끊는다는 결과는 같지만 마무리가 정반대입니다. \linebreak은 페널티를 놓을 뿐이므로 그 줄은 평소대로 양쪽 맞춤되어 오른쪽 여백까지 늘어납니다. 낱말 사이가 벌어지기 쉬운 이유가 이것입니다. \\는 앞서 본 \hfil로 남는 공간을 삼키므로 짧게 남습니다. “짧게 끊으려면 \\, 여백까지 맞추려면 \linebreak”로 외우면 헷갈리지 않습니다. 세부적으로 \@no@lnbk는 앞의 글루를 \unskip으로 걷어내고 페널티를 넣은 뒤 같은 폭의 글루를 되돌립니다. 그래서 word\linebreak word라고 써도 공백이 겹치지 않습니다.
여기서는 끊지 않기: 타이 ~와 \mbox가 치르는 대가
공백 하나만 지키면 되는 상황이라면 물결표 ~가 첫 선택입니다. 폭은 보통의 낱말 사이 공백과 같고, 다른 점은 그 자리에서 줄이 끊기지 않는다는 것뿐입니다. 숫자나 기호와 떼어 놓으면 안 되는 낱말에 씁니다. Fig.~3, Dr.~Smith, Chapter~12, equation~\ref{eq:1}처럼요. 중요한 것은 ~가 앞뒤 낱말의 하이픈 넣기를 막지 않는다는 점입니다. 내용은 \nobreak에 공백을 더한 것에 가까워서, 그 한 지점에 무한 페널티(\nobreak은 \penalty10000)를 놓을 뿐이므로 낱말 자체는 자유롭게 끊깁니다. 단계별 강도가 필요하면 \nolinebreak[n], 무조건 금지하려면 저수준의 \nobreak이 있습니다.
반면 \mbox{...}는 다른 물건입니다. 내용을 상자 하나로 묶어 버리므로 줄을 넘겨 끊을 수도, 하이픈을 넣을 수도 없게 됩니다. T-34처럼 쪼개면 안 되는 짧은 어구에는 알맞지만 대가가 있습니다. 실측: 폭 4.2cm의 minipage에 dichlorodiphenyltrichloroethane를 그대로 두면 들어가지만, \mbox{...}로 감싸면 Overfull \hbox (18.8814pt too wide)가 나오면서 낱말이 오른쪽 여백을 18.88pt 뚫고 나갑니다. 긴 URL이나 화합물 이름을 \mbox로 굳히는 것은 병보다 무거운 약입니다. 아래에서 다룰 \hyphenation{...}의 분할점 없는 등록이나 hyphenat의 \nohyphens{...}라면 상자를 만들지 않고 하이픈만 멈출 수 있습니다.
\-로 하이픈 자리를 추가하기: “여기서도”가 아니라 “여기서만”
단어 안에 \-를 하나만 넣어도 LaTeX은 그 단어를 표시한 자리에서만 끊고, 스스로 찾아낸 자리에서는 절대 끊지 않습니다. 패턴이 찾아 둔 후보가 전부 사라집니다. \showhyphens로 나란히 놓으면 한눈에 보입니다. 손대지 않은 supercalifragilistic는 su-per-cal-ifrag-ilis-tic로 다섯 군데에서 끊기지만, super\-califragilistic라고 쓰는 순간 super-califragilistic 한 군데로 줄어듭니다. hyphenation도 hy-phen-ation에서 hyphen\-ation이라고 쓰면 hyphen-ation이 됩니다. 즉 \-는 후보의 추가가 아니라 대체이며, 원하는 분할점은 전부 직접 적어야 합니다. 또한 \-는 끊기를 강제하지 않습니다. “끊는다면 이 후보들 중 하나”라고 말할 뿐입니다.
% Ask TeX where it would break, and read the answer in the .log:
% [] \T1/cmr/m/n/10 su-per-cal-ifrag-ilis-tic
\showhyphens{supercalifragilistic}
% [] \T1/cmr/m/n/10 super-califragilistic <- one \- killed the other four
\showhyphens{super\-califragilistic}
% So spell out every break point you actually want:
super\-cali\-fragi\-listiclong-standing의 뒷부분이 끊기지 않는 이유: hyphenat의 \hyp
직접 입력한 보통의 하이픈에도 같은 규칙이 적용되기 때문입니다. \showhyphens로 확인해 보면 longstanding은 long-stand-ing으로 세 조각이 되지만, long-standing이라고 쓰는 순간 long-standing——곧 직접 넣은 하이픈 자리만 남고 stand-ing이 사라집니다. well-known, X-ray, state-of-the-art 같은 복합어가 모두 여기에 해당합니다. 줄 끝에 긴 복합어가 오면 갑자기 조판이 힘들어지는 것이 이 규칙 때문입니다. 해법은 hyphenat 패키지의 \hyp입니다. long\hyp standing이라고 쓰면 실측에서 다시 long-stand-ing으로 돌아옵니다. 하이픈은 그대로 보이면서 패턴이 준 후보가 되살아납니다.
hyphenat에는 \mbox를 대신할 도구가 하나 더 있습니다. \nohyphens{...}는 그룹 안의 \language를 “패턴이 없는 언어”로 바꾸기만 하는 한 줄짜리 정의입니다. 실측에서도 \nohyphens{supercalifragilistic}의 후보는 0이었습니다. 상자를 만들지 않으므로 낱말 앞뒤에서는 지금까지처럼 줄이 끊깁니다. \mbox처럼 18pt씩 삐져나오는 사고가 없습니다. 같은 발상으로 \bshyp, \fshyp, \dothyp, \colonhyp는 URL과 경로의 \, /, ., :를 끊어도 되는 문자로 바꾸고, 패키지 옵션 [none]은 \hyphenpenalty=10000\exhyphenpenalty=10000 두 줄만으로 문서 전체의 하이픈 넣기를 멈춥니다.
\usepackage{hyphenat}
% A plain "-" kills the rest of the word: long-standing
% \hyp prints the same hyphen and keeps the patterns: long-stand-ing
long\hyp standing, well\hyp known, state\hyp of\hyp the\hyp art
% No hyphens in this word, but the line may still break around it.
% Unlike \mbox this cannot overflow the margin.
\nohyphens{supercalifragilistic}
% Breakable punctuation for URLs and paths:
https:\fshyp\fshyp example\dothyp com\fshyp very\fshyp long\fshyp path\hyphenation{...}이 듣지 않을 때: 악센트, 활용형, 대문자
같은 낱말이 거듭 나온다면 본문을 어지럽히지 말고 서문에서 예외를 한 번만 등록하면 됩니다. 형식은 커누스가 hyphen.tex 끝에서 한 것과 똑같습니다. 분할점을 하이픈으로 표시한 낱말을 \hyphenation{...}에 늘어놓기만 하면 됩니다. 이 장치에는 형식만 봐서는 드러나지 않는 쓰임이 하나 있습니다. 하이픈을 하나도 쓰지 않고 낱말을 등록하면 그 낱말은 다시는 끊기지 않습니다. 실측에서도 \hyphenation{fortran} 뒤에 \showhyphens{fortran}의 후보는 0이었습니다. 서두에 말한 present와 project가 커누스의 예외 목록에 분할점 없이 실린 이유가 바로 이것입니다. \mbox와 달리 상자를 만들지 않으므로 여백을 넘칠 걱정도 없습니다.
듣지 않는 이유도 세 가지를 실측했습니다. 첫째는 악센트입니다. 기본 글꼴 인코딩인 OT1에서 \hyphenation{r\'e-sum\'e}라고 쓰면 ! Improper \hyphenation will be flushed.로 멈춥니다. OT1의 악센트 문자는 \accent로 합성된 “글자가 아닌 것”이기 때문입니다. \usepackage[T1]{fontenc}를 넣으면 단일 글리프가 되어 같은 줄이 아무 일 없이 통과합니다. 둘째는 완전 일치입니다. \hyphenation{hyphena-tion}을 등록하면 hyphenation은 hyphena-tion이 되지만, 복수형 hyphenations는 예외에 걸리지 않고 패턴으로 돌아가 hy-phen-ations가 됩니다. 활용형은 하나씩 등록해야 합니다. 셋째, 대소문자는 구분하지 않습니다. TeX은 \lccode로 소문자화한 뒤 대조하므로 For-tran 한 건만으로 FORTRAN, Fortran, fortran이 각각 FOR-TRAN, For-tran, for-tran으로 끊겼습니다.
\usepackage[T1]{fontenc} % without this, an accent in \hyphenation errors:
% ! Improper \hyphenation will be flushed.
\hyphenation{% one entry per inflected form; case does not matter
hyphena-tion hyphena-tions
r\'e-sum\'e
fortran % no hyphens = never break this word
}babel의 언어별 하이픈 패턴: Wissenschaft가 두 가지로 끊긴다
하이픈 패턴은 언어마다 다르며, \language를 바꾸면 같은 낱말이 다른 자리에서 끊깁니다. 실측이 모든 것을 말해 줍니다. \usepackage[english,ngerman]{babel}을 읽어 들이면 \language가 36(ngerman)이 되고, \showhyphens{Wissenschaft Wahrscheinlichkeit}는 올바른 Wis-sen-schaft Wahr-schein-lich-keit를 냅니다. 그런데 \selectlanguage{english}(\language는 0)로 바꾼 직후 같은 낱말이 Wis-senschaft Wahrschein-lichkeit가 됩니다. 독일어로는 틀린 분할입니다. 반대도 마찬가지여서, 독일어 패턴으로 영어를 조판하면 typesetting이 ty-pe-set-ting이 됩니다. babel 없이 쓴 독일어 논문이 묘하게 어색한 이유는 대개 이것입니다.
여기서 직접적인 귀결이 나옵니다. \hyphenation{...}의 예외 목록도 \language마다 따로 보관되므로, 선언한 시점에 유효했던 언어에만 적용됩니다. 실측: \usepackage[english,ngerman]{babel} 뒤 서문에서 \hyphenation{Wiss-en-schaft}라고 쓰면 ngerman 본문에서는 지정대로 Wiss-en-schaft로 끊기지만, \selectlanguage{english} 직후에는 예외가 통째로 보이지 않게 되어 영어 패턴의 Wis-senschaft로 돌아갑니다. 다국어 문서에서는 예외 등록을 목표 언어를 선택한 다음에 쓴다는 것이 철칙입니다. 일본어에는 이런 의미의 하이픈 넣기가 없으므로 \-도 \hyphenation도 서양어 부분을 위한 도구입니다.
\usepackage[T1]{fontenc}
\usepackage[english,ngerman]{babel}
% Exceptions belong to the language that is current when they are declared.
% Select the language first, then declare.
\selectlanguage{ngerman}
\hyphenation{Wiss-en-schaft}
\selectlanguage{english}
\hyphenation{hyphena-tion hyphena-tions}
% Check the result rather than guessing:
% ngerman -> Wis-sen-schaft english -> Wis-senschaft
\showhyphens{Wissenschaft}| 명령 | 역할 | 줄을 늘리는가 |
|---|---|---|
\\ , \newline | 줄을 강제로 끊습니다(문단은 그대로). [길이]와 *는 \\만 받습니다 | 아니요. \hfil이 남는 공간을 삼켜 짧게 남습니다 |
\linebreak[n] | 페널티 −(0/51/151/301/10000)를 넣습니다. 기본값 n=4 | 예. 오른쪽 여백까지 양쪽 맞춤됩니다 |
\nolinebreak[n] | 페널티 +(0/51/151/301/10000)를 넣습니다. n=4는 완전 금지 | — |
~ | 끊기지 않는 낱말 사이 공백. 하이픈 넣기는 막지 않습니다 | — |
\mbox{...} | 내용을 상자 하나로. 끊기도 하이픈도 불가. 넘침의 원인이 됩니다 | — |
\- | 임의 하이픈. 그 낱말은 표시한 자리에서만 끊깁니다 | — |
\hyp | hyphenat. 하이픈은 찍으면서 낱말 전체의 후보를 남깁니다 | — |
\nohyphens{...} | hyphenat. 하이픈만 멈춥니다. 상자가 아니므로 넘치지 않습니다 | — |
\hyphenation{...} | 서문의 예외 목록. 완전 일치, 대소문자 무관, \language별로 따로 | — |
\showhyphens{...} | 진단용. 그 낱말의 현재 분할점을 .log에 적어 줍니다 | — |
줄 바꿈 경고를 고치는 순서: 힘으로 하는 것은 마지막에
경고가 나온 자리에 \\를 박아 넣어 지우는 것이 가장 하면 안 되는 수정입니다. TeX의 줄 나눔은 문단 전체를 보고 최적화하므로, 한 줄을 고정하면 나머지 배분이 바뀌어 다른 줄이 망가집니다. 순서는 원고 고쳐 쓰기 → 하이픈 후보 늘리기 → ~나 \nolinebreak으로 국소적으로 금지하기 → 마지막에 문단 전체의 허용도 완화하기입니다. 그 마지막 단계——\tolerance, \emergencystretch, \sloppy(기본으로 되돌리는 것은 \fussy), 그리고 Overfull \hbox 읽는 법——는 “Overfull \hbox 다루기” 페이지가 맡습니다(\sloppy가 사실 네 줄의 대입에 지나지 않는다는 것도 그쪽에서 실측했습니다). microtype으로 글자폭을 미세하게 늘였다 줄이는 방법도 거기에 있습니다.
- 긴 낱말・URL・화합물 이름을 먼저 추려냅니다.
\showhyphens{...}에 넣어 애초에 끊을 자리가 있는지 확인합니다. - 전문 용어에는
\hyphenation{...}항목을. 활용형마다 하나씩, 다국어 문서라면\selectlanguage뒤에 적습니다. \-는 남은 한 낱말에만. 하나만 넣어도 그 낱말의 다른 후보가 전부 사라진다는 점을 잊지 마세요.- 사람 이름・번호・단위는
~로 묶습니다.\mbox는 정말로 쪼개면 안 되는 짧은 어구에만 씁니다. - 다국어 문서에는 반드시
babel을 넣습니다. 언어를 바꾸지 않으면 한쪽 언어는 늘 엉뚱한 자리에서 끊깁니다.