fontspec(Xe/Lua)

같은 세 줄짜리 프리앰블이 LuaLaTeX에서는 통과하고 XeLaTeX에서는 ! Package fontspec Error: The font "TeX Gyre Termes" cannot be found.로 멈춥니다. 환경이 망가진 것이 아니라 fontspec의 설계가 그렇습니다. fontspec은 LaTeX에서 글꼴을 서체 이름으로 부르게 해 주는 패키지라, \setmainfont 한 줄이면 본문 서체가 바뀝니다. 다만 그 이름을 “누구에게 묻는가”가 엔진마다 달라서, 실제로 문서를 망가뜨리는 지점은 거기 하나뿐입니다. 이 페이지는 이름 해석의 어긋남을 한가운데 놓고 \setmainfont, \newfontfamily, 파일 지정, OpenType 기능 키까지 실측과 함께 훑습니다.

fontspec이 동작하는 엔진, 그리고 pdfLaTeX에서 안 되는 이유

fontspec이 동작하는 엔진은 XeLaTeX와 LuaLaTeX 둘뿐이고, pdfLaTeX에서는 \usepackage{fontspec} 줄 자체에서 오류가 납니다. 이유는 역사적입니다. 1980년대의 TeX는 자체 TFM 메트릭과 Type 1 / METAFONT 글꼴만 압니다. OpenType도, 시스템 전역 글꼴 데이터베이스라는 발상도 TeX가 만들어진 뒤에 세상이 마련한 것입니다. XeTeX와 LuaTeX는 그 세상에 맞추어 바깥에서 다시 만든 엔진이라 Unicode 입력과 OpenType 글꼴을 네이티브로 처리합니다. fontspec은 그 새로운 능력을 LaTeX의 어휘로 옮기는 층이며, pdfTeX에는 옮길 능력 자체가 없습니다.

읽어들이는 것은 \usepackage{fontspec} 한 줄입니다. XeLaTeX·LuaLaTeX용 클래스와 패키지(bxjsarticle, unicode-math, luatexja-fontspec 등)는 대개 내부에서 먼저 읽어들이며, 나중에 명시적으로 써도 이중 로드가 되지 않습니다. pdfLaTeX 문서를 옮겨 올 때는 \usepackage[T1]{fontenc}\usepackage[utf8]{inputenc}를 빼는 것을 잊지 마세요. 앞의 것은 fontspecTU 인코딩으로 바꾸므로 필요 없고, 뒤의 것은 Unicode 엔진에서는 의미가 없습니다.

\setmainfont / \setsansfont / \setmonofont으로 본문 서체 바꾸기

문서를 떠받치는 세 명령은 \setmainfont{…}(로마체, 곧 본문 기본 서체), \setsansfont{…}(\textsf·\sffamily가 고르는 서체), \setmonofont{…}(\texttt·\ttfamily 뒤의 등폭 서체)입니다. 인수는 서체 이름이나 글꼴 파일 이름입니다. fontspec은 거기서 정체·굵은꼴·이탤릭·굵은 이탤릭이라는 네 짝을 자동으로 찾아 \textbf, \textit, \emph에 연결하므로 본문을 쓰는 방식은 전혀 달라지지 않습니다. 이 네 짝을 자동으로 맞추는 일이 fontspec의 핵심 업무이며, 뒤에서 보듯 그 자동화가 무너질 때만 설정을 덧붙이게 됩니다.

document.tex
% compile with lualatex (or xelatex — see the next section)
\documentclass{article}
\usepackage{fontspec}
\setmainfont{TeX Gyre Termes}   % roman / body
\setsansfont{TeX Gyre Heros}    % sans serif
\setmonofont{TeX Gyre Cursor}   % monospaced
\begin{document}
Body text, \textbf{bold}, \textit{italic}, \textsf{sans}, \texttt{mono}.
\end{document}

이 명령들은 원칙적으로 프리앰블에 씁니다. 이유는 하나, LaTeX가 수식용 글꼴을 굳히는 시점이 \begin{document}이기 때문입니다. 프리앰블에서 \setmainfont를 실행하면 같은 서체가 \mathrm 같은 수식 안 로마체에도 미치지만, 본문에서 실행하면 본문만 바뀌고 수식의 로마체는 남겨집니다. 수식 자체의 서체는 fontspec의 관할 밖이며 unicode-math\setmathfont가 담당합니다. 본문과 수식이 서로 다른 명령으로 움직인다는 점을 기억해 두세요.

The font "…" cannot be found. — XeLaTeX와 LuaLaTeX는 찾는 곳이 다릅니다

! Package fontspec Error: The font "…" cannot be found.XeLaTeX에서만 나온다면 이름을 잘못 쓴 것이 아니라 묻는 상대가 다른 것입니다. XeTeX은 운영체제의 글꼴 데이터베이스에 물어봅니다. 그래서 OS가 설치되어 있다고 보는 서체만 볼 수 있습니다. LuaTeX은 luaotfload가 자체 색인을 만들고, 그 색인에는 TeX 트리 안까지 들어갑니다. TeX Live가 함께 배포하는 글꼴은 OS에 등록되어 있지 않으므로 이 차이가 그대로 결과의 차이가 됩니다. 이 컴퓨터의 TeX Live 2024에서 확인한 예에서는 같은 파일이 LuaLaTeX에서는 통과하고 XeLaTeX에서는 멈췄습니다.

terminal
$ xelatex t1.tex
! Package fontspec Error: The font "TeX Gyre Termes" cannot be found.

$ lualatex t1.tex
Output written on t1.pdf (1 page, 3697 bytes).

$ pdffonts t1.pdf
name                            type          encoding    emb sub uni
NNOCLB+TeXGyreTermes-Regular    CID Type 0C   Identity-H  yes yes yes

경계는 “함께 배포되는 글꼴이냐, OS의 글꼴이냐”로 그어집니다. 실측해 보면 Hiragino Mincho ProN, Hiragino Sans, YuMincho처럼 OS 쪽에 설치된 서체는 XeLaTeX에서도 그대로 해결됩니다. 반대로 TeX Live가 texmf-dist/fonts/opentype/public/ 아래에 배포하는 서체는 XeLaTeX에서 이름으로 보이지 않습니다. TeX Live 2024에서 이 디렉터리에는 125개의 패키지(실제 .otf 파일은 1,499개)가 들어 있습니다. 엔진 자체의 설계 사상은 /learn/engines/xetex/learn/engines/luatex가 다룹니다. 여기서 필요한 것은 망가진 문서를 지금 당장 고치는 절차입니다.

고치는 방법은 둘입니다. (1) lualatex로 컴파일한다. 소스를 한 줄도 고칠 필요가 없어 대개 가장 빠릅니다. (2) 서체 이름 대신 파일 이름을 쓴다. \setmainfont{texgyretermes-regular.otf}는 XeLaTeX에서도 통합니다. 다만 대가가 있어, 파일 이름으로 부르면 fontspec은 굵은꼴과 이탤릭을 추측하지 못합니다. 실제로 \textbf를 쓰면 LaTeX Font Warning: Font shape 'TU/texgyretermes-regular.otf(0)/b/n' undefined가 나오고, 굵게 만들려던 부분이 가는 채로 출력됩니다. 오류가 아니라 경고라서 지나치기 쉬운 전형적인 조용한 실패입니다. 파일 이름은 대소문자도 구별합니다. TeXGyreTermes-Regular.otf라고 쓰면 같은 서체인데도 다시 cannot be found로 돌아갑니다.

파일로 지정하기: Path= / Extension= / BoldFont=

파일 지정의 완전한 형태는 네 가지 스타일을 하나씩 손으로 배정하는 방식입니다. Path=로 디렉터리를, Extension=로 확장자(.otf.ttf)를 주고, UprightFont=, BoldFont=, ItalicFont=, BoldItalicFont=에 각 파일을 대응시킵니다. 값 안의 별표 *는 첫 인수에 쓴 공통 기본 이름으로 대체되는 자리표시자입니다. 앞 절에서 사라졌던 굵은꼴과 이탤릭은 이 넷을 쓰면 돌아옵니다. 실측에서도 넷을 모두 준 판본은 Font shape … undefined 경고가 0이었고, pdffonts에 Regular·Bold·Italic·BoldItalic 넷이 나란히 나왔습니다.

document.tex
\usepackage{fontspec}
% XeLaTeX-safe: name the files, and name all four styles
\setmainfont{texgyretermes}[
  Extension      = .otf ,
  UprightFont    = *-regular ,
  BoldFont       = *-bold ,
  ItalicFont     = *-italic ,
  BoldItalicFont = *-bolditalic ,
]

% fonts shipped inside the project directory
\setsansfont{LibreBaskerville}[
  Path        = ./fonts/ ,
  Extension   = .otf ,
  UprightFont = *-Regular ,
  BoldFont    = *-Bold ,
]

어느 쪽을 고를지는 실무적으로 그 문서를 누가 조판하는가로 정해집니다. 자기 환경에서만 볼 초안이라면 서체 이름이 빠릅니다. 공저·투고·CI·Docker처럼 다른 사람이나 다른 기계에서도 같은 PDF가 나와야 한다면, 프로젝트 안에 글꼴을 함께 넣고 Path=로 가리키는 방식이 사실상 유일하게 확실한 답입니다. Path=가 주어지면 fontspec은 TeX의 검색 경로가 아니라 그 상대 경로를 직접 보므로, 저장소를 통째로 옮길 수 있습니다.

지정 방법쓰는 법어울리는 상황XeLaTeX에서 동봉 글꼴을 찾는가
By name\setmainfont{TeX Gyre Termes}초안과 개인 문서. 굵은꼴과 이탤릭이 자동으로 맞춰집니다아니요. OS에 등록된 서체만
By file, upright only\setmainfont{texgyretermes-regular.otf}일단 통과시키고 싶을 때. 굵은꼴은 사라집니다예. 다만 \textbf가 조용히 가는 채로 남습니다
By file, all fourPath / Extension / UprightFont / BoldFont / ItalicFont / BoldItalicFont공동 집필, 투고, CI, 자작 글꼴. 재현성이 최우선일 때예. Font shape … undefined 경고도 나오지 않습니다

\newfontfamily로 네 번째·다섯 번째 서체 더하기

로마체·산세리프·등폭 세 자리에 들어가지 않는 서체는 \newfontfamily로 자기 전환 명령을 만들어 더합니다. 첫 인수는 만들 명령 이름, 둘째 인수는 서체 이름이며, \setmainfont와 같은 옵션을 그대로 받습니다. 만들어진 명령은 \rmfamily와 같은 선언형이라 그 뒤 전부에 적용됩니다. 범위를 제한하려면 {\titlefont …}처럼 중괄호로 감싸세요.

document.tex
\usepackage{fontspec}
\newfontfamily\titlefont{TeX Gyre Bonum}
\newfontfamily\quotefont{TeX Gyre Schola}[Scale = MatchLowercase]
% ...
\titlefont A heading in Bonum

{\quotefont A quoted passage in Schola.}

한 번만 쓸 서체라면 정의하지 않고 \fontspec{서체 이름}을 그 자리에서 선언으로 쓸 수 있습니다. 다만 \fontspec은 불릴 때마다 글꼴을 다시 읽어들이므로, 장 제목처럼 여러 번 나오는 서체에 쓰면 컴파일 시간만 조금씩 늘어납니다. 두 번 이상 쓴다면 \newfontfamily가 정석입니다. 또한 같은 이름의 명령이 이미 있으면 \newfontfamily는 실패합니다. \sc\it처럼 LaTeX가 이미 쓰는 짧은 이름은 피하고 \titlefont처럼 용도를 알 수 있는 이름을 붙이세요.

키로 OpenType 기능 부르기: Ligatures / Numbers / Scale

OpenType 글꼴 안에는 합자, 숫자 이형, 작은 대문자 같은 대체 글리프가 처음부터 들어 있고, fontspec은 이를 key=value 옵션으로 꺼냅니다. 효과는 눈으로 보는 것보다 재는 편이 빠르므로, TeX Gyre Pagella 10pt에서 실측한 값을 곁들입니다. Numbers=OldStyle을 걸면 0123456789의 깊이가 0.19998pt에서 2.37999pt로 늘어납니다. 구식 숫자가 39에 디센더를 준다는 사실이 수치에 그대로 나타납니다.

  • Ligatures= — 합자와 입력 변환. Ligatures=TeX--를 en dash로, ---를 em dash로, `''를 굽은 따옴표로 바꾸는 **TeX식 표기법**을 켭니다(실측: ---가 “하이픈 셋”의 9.99pt에서 정확히 10.0pt, 곧 1 em짜리 진짜 em dash로 바뀝니다). 그 밖에 Common(fi, fl 등 표준 합자), NoCommon(그것을 끔; 실측으로 fi 폭이 6.05pt → 6.35pt), Rare/Discretionary, Historic`이 있습니다.
  • Numbers= — 숫자 스타일. OldStyle(본문에 어울리는 구식 숫자) 또는 Lining(높이가 고른 현대 숫자)을 Proportional 또는 Monospaced와 조합합니다. 표 안의 숫자는 Lining,Monospaced로 두면 자릿수가 세로로 맞습니다.
  • Letters=SmallCaps — 진짜 작은 대문자. Libertinus Serif로 실측하면 Abc의 높이가 6.98pt → 6.58pt, 폭이 16.26pt → 16.94pt가 됩니다. 주의: 글꼴에 smcp 표가 없으면 fontspec은 아무 일도 하지 않고 경고도 내지 않습니다(TeX Gyre Pagella에서는 폭이 17.6pt 그대로였습니다). Letters=Uppercase 같은 값도 있습니다.
  • SmallCapsFeatures= — 작은 대문자일 때만 적용할 기능을 묶어서 넘깁니다(예: SmallCapsFeatures={Numbers=OldStyle}). 같은 방식으로 UprightFeatures=, BoldFeatures=, ItalicFeatures=가 있어 스타일별로 따로 설정할 수 있습니다.
  • Scale= — 배율. 숫자 외에 Scale=MatchLowercase(본문 글꼴의 소문자 높이에 맞춤)와 Scale=MatchUppercase(대문자 높이에 맞춤)가 있습니다. 실측에서 TeX Gyre Heros의 x-height 5.24pt가 Pagella 본문에 맞춰 4.68999pt, 곧 Pagella의 4.69pt와 정확히 일치했습니다.
  • StylisticSet= — 글꼴이 제공하는 대체 글리프 세트를 번호로 고릅니다. RawFeature=는 전용 키가 없는 기능을 OpenType 태그로 직접 넘기는 우회로이며, RawFeature={+onum}Numbers=OldStyle과 같은 결과를 냅니다(실측 깊이도 같은 2.37999pt).
  • Script= / Language= — 문자 체계와 언어를 지정합니다(Script=Arabic, Language=Turkish 등). Renderer=는 어떤 셰이핑 엔진을 쓸지 정하며, XeTeX에서는 AAT, OpenType, Graphite 중에서 고를 수 있습니다. LuaTeX에서는 luaotfload가 처리하므로 이 키는 대개 필요 없습니다.
document.tex
\usepackage{fontspec}
\setmainfont{TeX Gyre Pagella}[
  Ligatures = TeX ,
  Numbers   = OldStyle ,
  SmallCapsFeatures = {Numbers = Lining} ,
]
% match the sans to the body x-height
\setsansfont{TeX Gyre Heros}[Scale = MatchLowercase]

문서 전체가 아니라 한 곳에만 기능을 더하고 싶을 때는 \addfontfeature{…}(여러 개라면 \addfontfeatures{…})를 씁니다. 지금 유효한 서체에 지정한 기능을 국소적으로 덮어쓰는 명령이며, 감싼 중괄호 안에서만 효과가 있습니다. 구식 숫자로 조판한 문서에서 표 안의 숫자만 자릿수를 맞추고 싶을 때가 바로 이 경우입니다.

latex
Set in old-style figures, but this column
{\addfontfeature{Numbers={Lining,Monospaced}} 01234 56789}
lines up digit by digit.

fontspec이 담당하지 않는 것: 수식과 일본어

fontspec이 맡는 것은 본문의 라틴 서체뿐이고, 수식과 일본어는 별도의 계통을 씁니다. 수식은 unicode-math를 읽어들여 \setmathfont{…}에 OpenType 수식 글꼴(Latin Modern Math, STIX Two Math, NewCM Math 등)을 넘깁니다. \setmainfont를 바꿔도 수식은 전혀 움직이지 않으므로, 본문과 수식의 인상이 어긋나는 문제는 pdfLaTeX만의 이야기가 아닙니다. 어떤 수식 글꼴을 고를지는 수식 서체 페이지의 몫입니다.

일본어도 구도가 같습니다. \setmainfont는 라틴 서체만 바꾸고 한자와 가나에는 아무 일도 일어나지 않습니다. LuaLaTeX에서는 fontspec의 일본어판이라 할 luatexja-fontspec을 읽어들이고, \setmainjfont{…}(명조, 일본어 본문), \setsansjfont{…}(고딕), \newjfontfamily 같은 “j” 붙은 명령을 씁니다. 옵션 문법은 거의 같아서 Scale=Path=에 대한 지식을 그대로 가져올 수 있습니다. 즉 Unicode 엔진에서의 일본어 조판은 두 계통을 나란히 굴리는 형태입니다. 어떤 일본어 글꼴을 고를 수 있는지는 일본어 글꼴 페이지에서 다룹니다.