CJK (pdfLaTeX / XeLaTeX)

TeX 是围绕 256 个字符的字体设计的,而汉字有数万个。1994 年起步的 CJK 宏包给出的答案直白得近乎粗暴:把字体切成一片片 256 个字形的碎块,下一个字落在哪一块就载入哪一块。今天用 pdfLaTeX 排一段中文,PDF 里嵌入的仍然就是这个东西——gbsnu4egbsnu5bgbsnu65,每个 Unicode 区段一个子字体。本页讲的是 pTeX 世界之外的 CJK:pdfLaTeX 上的 CJK/CJKutf8、XeLaTeX 上的 xeCJK、面向中文的 ctex,以及面向韩文的 kotex。这几条路都先在字体上翻车,所以文中也写明一套原装的 TeX Live 2024 到底给了你哪些 CJK 字体。

在 pdfLaTeX 上排中文和韩文 — CJKutf8 依然可用

被绑在 pdfLaTeX 上并不妨碍排中文或韩文。 在 TeX Live 2024 上,只要加一句 \usepackage{CJKutf8} 并把正文包进 CJK* 环境,完全不用另装字体就能通过。实测简体(gbsn)、繁体(bsmi)、韩文(mj)和日文(min)都是一次编译成功,PDF 里嵌入的是 Type 1 子字体。宏包出自 Werner Lemberg 之手,版权声明写着 1994–2021,TeX Live 内含的版本是 2021 年 10 月 16 日的 4.8.5。它是被冻结,而不是坏掉了。代价在于:CJK 只能读取事先为 TeX 转换好的字体,装在操作系统里的 OpenType 字体它够不着。正是这一条把它和 XeLaTeX 分了开来。

latex
% pdflatex — simplified Chinese, no extra fonts needed
\documentclass{article}
\usepackage{CJKutf8}
\begin{document}
\begin{CJK*}{UTF8}{gbsn}
这是一段中文,混排 English words 也没问题。
\CJKfamily{gkai}换成楷体。
\end{CJK*}
\end{document}

环境的第一个参数是编码,第二个参数是字体族,而能写什么完全取决于你手上有哪些预先转换过的字体。在 TeX Live 2024 上原封不动即可通过的有下表八个族。只有 song 失败,报 Font cyberb30 at 600 not found——它预设了需要另行获取的 Cyberbit 字体。另一件人人都会踩一次的事,是把汉字写到环境外面。那会得到 ! LaTeX Error: Unicode character 日 (U+65E5) not set up for use with LaTeX.,而且每个字符一条错误,落在外面的一整段就是几十条。看日志时要看第一条错误,而不是错误的数量。

字体族语言实际字体TeX Live 2024
gbsn简体中文文鼎 GB 宋体(arphic已同捆,可直接编译
gkai简体中文文鼎 GB 楷体(arphic已同捆,可直接编译
bsmi繁体中文文鼎 Big5 明体(arphic已同捆,可直接编译
bkai繁体中文文鼎 Big5 楷体(arphic已同捆,可直接编译
mj韩文UHC 明朝(uhc已同捆;kotex 是更好的选择
min日文Wadalab 明朝(wadalab已同捆;upLaTeX 是更好的选择
goth日文Wadalab 哥特体(wadalab已同捆,可直接编译
maru日文Wadalab 圆体(wadalab已同捆,可直接编译
song中文Cyberbit(需另行获取)Font cyberb30 at 600 not found 而失败

CJKCJK* 之别不是装饰,而是有没有实际损害。CJK.sty 里的注释说得直白:CJK* 会吞掉 CJK 字符后面的空白,CJK 不会。真实稿件中,一句中文在源文件里必然要断行,而这个换行在 TeX 眼里就是一个空格。在 CJK 环境中的结果是 汉字之间不应该 出现空格。,句子中间留了个口子;在 CJK* 环境中则正确闭合为 汉字之间不应该出现空格。中文和日文都应该默认选 CJK* 同理,\begin{CJK*}{UTF8}{gbsn} 之后一定要换行——留在同一行上的字符会在 CJK 含义生效之前就被读走。中途要换字体就用 \CJKfamily{gkai},无须嵌套环境。

若非得用 pdfLaTeX 排日文,架在 CJK 之上的 bxcjkjatype(ZR 开发;TeX Live 2024 内含 v0.5,2023 年 7 月 23 日)会轻松得多。写上 \usepackage[whole]{bxcjkjatype},整篇文档会被自动包起来,无须自己写 CJK* 环境,换行造成的空白与和文欧文之间的间距也都由它照管。实测中排版正确闭合,嵌入的是把 IPAex 明朝转成 Type 1 的 ipaex-type1。不过这终究是被钉在 pdfLaTeX 上时的权宜之计;引擎能选的话,请继续往下看。

XeLaTeX 上的 CJK — xeCJK 字体设置在哪里出错

xeCJK 只能配 XeLaTeX,而能不能编译通过,往往取决于字体名怎么写。 什么都不设、只写 \usepackage{xeCJK},实测也能零错误产出 PDF,因为默认会载入 Fandol Song。但 Fandol Song 是中文字体。在同一份文档里混入韩文,日志就会排出 Missing character: There is no 한 (U+D55C) in font [FandolSong-Regular.otf],而退出码仍是 0,韩文却从 PDF 里彻底消失。抽取文本核对后确认:那句韩文根本不存在。要用 xeCJK 排多文种文档,就必须按文种分别指定字体。

latex
% xelatex — one font per script; file names, not family names
\documentclass{article}
\usepackage{xeCJK}
\setCJKmainfont{FandolSong-Regular.otf}[
  BoldFont   = FandolSong-Bold.otf,
  ItalicFont = FandolKai-Regular.otf]
\setCJKsansfont{FandolHei-Regular.otf}
\newCJKfontfamily\korean{UnBatang.ttf}
\begin{document}
这是一段中文。{\korean 한국어 문장입니다.}
\end{document}

真正咬人的地方在于 XeTeX 是向操作系统要字体的\setCJKmainfont{FandolSong-Regular.otf} 是文件名,能在 TeX 目录树里解析,所以可用;把同一款字体写成字体族名 \setCJKmainfont{Fandol Song},就会以 ! Package fontspec Error: The font "Fandol Song" cannot be found. 告终——尽管它就在 TeX Live 里。反过来,操作系统认识的字体——macOS 上的 Songti SCPingFang SCHiragino Sans——用族名指定畅通无阻。到处被复制的 \setCJKmainfont{Noto Sans CJK SC} 同样会失败,因为 Noto CJK 并不随 TeX Live 分发;除非你自己装过,否则只会得到 The font "Noto Sans CJK SC" cannot be found.。规则很简单:TeX 目录树里的字体用文件名(含扩展名),操作系统里的字体用族名。 这一行为本身在 XeTeX 页面有更详细的讨论。

字体解决之后,接着是间距。xeCJK 通过 \xeCJKsetup{...} 暴露字间距设置:CJKglue 是汉字之间的间距,CJKecglue 是汉字与拉丁文之间的间距。TeX Live 2024 上的默认值分别是 0pt plus 0.08\baselineskip~——也就是说,汉字与英文单词之间会插入一个完整的词间空格。对熟悉 pTeX 的读者:这与 \kanjiskip\xkanjiskip 是同一个思路,只是默认值不同;那一套由日文排版细节页面负责。标点压缩用 PunctStyle,标点之间能否断行用 AllowBreakBetweenPuncts。另外,在 LuaLaTeX 下载入 xeCJK 会停在 ! Critical Package xeCJK Error: The xeCJK package requires XeTeX to function.。这个名字不是装饰。

中文就用 ctex — 用法与 fontset 的坑

排中文文档,直接用 ctex 比手工配置 xeCJK 省事得多。 要么 \documentclass{ctexart}(还有 ctexrepctexbookctexbeamer),要么在既有文档类上加一句 \usepackage[UTF8]{ctex},字体、行距、中文标题格式、标点压缩、章节号的中文数字就都一次到位。ctex 会自动判别引擎,机制朴素得很:看有没有名为 ctex-engine-<引擎名>.def 的文件。支持的是 xelatex、lualatex、pdflatex、uplatex、aplatex;其余的会以 Engine '...' is not yet supported, ctex will abort! 停下。TeX Live 2024 内含的版本是 2.5.10。

latex
% xelatex or lualatex — fandol is the only fontset TeX Live guarantees
\documentclass[fontset=fandol]{ctexart}
\begin{document}
\section{中文标题}
这是一段中文,混排 English words。今天是\today\end{document}

麻烦不在引擎,而在字体集。ctex 还会尝试判别操作系统——Windows 用 windows,macOS 用 mac,其余用 fandol。macOS 的判定方式很有意思:它检查 /System/Library/Fonts/Menlo.ttc 这个文件在不在。一个排版宏包,靠一款程序员等宽字体的有无来认操作系统。猜对时舒服,猜错时就摔得响。实测中,不写 fontset 直接跑 ctexart,XeLaTeX 通过了,而 LuaLaTeX 以 ! Package fontspec Error: The font "STHeiti" cannot be found. 失败,pdfLaTeX 以 ! Critical Class ctexart Error: CTeX fontset 'mac' is unavailable in current mode. 失败。结论只有一条:fontset= 一定要自己写。 TeX Live 必然带有的中文字族只有 Fandol,所以要发给别人的文档用 fontset=fandol 最稳。即便如此,pdfLaTeX 仍会报 CTeX fontset 'fandol' is unavailable in current mode.,因为 fandol 只服务 Unicode 引擎。pdfLaTeX 下的 ctex 内部载入 CJKutf8CJKpunct/CJKspace,并预设 Windows 字体。若非得用 pdfLaTeX 排中文,退回 CJKutf8gbsn 比死磕 ctex 现实得多。

韩文用 kotex — 一行搞定,任何引擎都行

韩文只需一行 \usepackage{kotex},在 pdfLaTeX、XeLaTeX、LuaLaTeX 上都能跑通。 这是 CJK 三种语言里最省心的局面,打开 kotex.sty 就明白为什么:这个文件与其说是宏包,不如说是一个分发器。它检查有没有 \directlua\XeTeXrevision,然后载入三个内部完全不同的宏包之一——pdfTeX 下是 kotexutf(cjk-ko),XeTeX 下是 xetexko,LuaTeX 下是 luatexko。选项会原样转交给下游,所以作者根本不必操心走的是哪条路。字体也确实随发行版附带:实测中 pdfLaTeX 路线嵌入了 Nanum 明朝的 Type 1 子字体(nanumtype1),XeLaTeX 路线嵌入了 UnBatang(unfonts-core),两者的文本抽取都正确。

latex
% pdflatex, xelatex or lualatex — all three work unchanged
\documentclass{article}
\usepackage[hangul]{kotex}
\begin{document}
\section{한국어 시험}
오늘은 \today 입니다.
\end{document}

加上 [hangul] 之后,文档字符串和日期也会变成韩文:实测中 \today 变为 2026년 8월 13일,图题变为 그림 1:(不加这个选项则仍是 August 13, 2026)。换字体时,XeLaTeX 的名称解析怪癖又会咬人一口——\setmainhangulfont{UnBatang}! Package fontspec Error: The font "UnBatang" cannot be found. 告终,而 \setmainhangulfont{UnBatang.ttf} 通过了。UnBatang.ttf 明明就在 TeX 目录树里,用族名却找不到。你也可以直接载入 xetexko,但走 kotex 意味着源文件不把引擎写死,合作写作和投稿时更省事。

TeX Live 里实际有哪些 CJK 字体

TeX Live 2024 的字体目录树里根本没有 Noto CJK。 名为 NotoSansCJKNotoSerifCJK 的文件一个都找不到;TeX Live 的 noto 宏包实际提供的是 NotoSerif、NotoSans、NotoSansMono,也就是拉丁字族。网上到处能看到的 \setCJKmainfont{Noto Sans CJK SC} 之所以能跑,只是因为写它的人在操作系统里装了 Noto CJK。把这一行写进要分发的文档或容器镜像,到别人机器上必然失败。真正随发行版附带的见下表,可以看出中文这一侧有多单薄:OpenType 只有 Fandol 一族,此外就只有为 CJK 宏包预先转换过的文鼎字体。日文(Harano Aji、IPAex)与韩文(Un 字体、Baekmuk、Nanum)的余地要大得多。

TeX Live 宏包语言格式适用路线
fandol中文OpenTypexeCJK 默认值与 ctexfontset=fandol
arphic中文Type 1 子字体CJKutf8gbsn/gkai/bsmi/bkai
arphic-ttf中文TrueTypeUnicode 引擎,按文件名指定
haranoaji日文OpenTypeLuaTeX-ja、zxjatype 等的默认字体
ipaex日文TrueTypeUnicode 引擎,按文件名指定
wadalab日文Type 1 子字体CJKutf8min/goth/maru
unfonts-core韩文TrueTypexetexko/luatexko 的默认字体
nanumtype1韩文Type 1 子字体pdfLaTeX 上的 kotex(cjk-ko)
uhc韩文Type 1 子字体CJKutf8mj

该走哪条路

引擎能自己挑的时候:中文用 ctex 配 XeLaTeX 或 LuaLaTeX,韩文用 kotex,日文用 upLaTeX 或 LuaTeX-ja。 只有当文档类或模板把你钉在 pdfLaTeX 上时——通常是期刊的投稿规定——才退到 CJK/CJKutf8。日文是唯一算法不同的语言:一旦牵扯禁则、JFM、注音和竖排,xeCJK 就够不着 pTeX 家族和 LuaTeX-ja 积累下来的东西。日文路线的比较归“日文排版方法”页面,细致的间距调整归“日文排版细节”页面。跨语言的文档——比如德文正文里插一段中文引文——话题会转到 babelpolyglossia 和字体切换,那是“多语言”页面的主场。

  • 中文 → 用 XeLaTeX 或 LuaLaTeX 跑 \documentclass[fontset=fandol]{ctexart}fontset 一定要写明。
  • 韩文\usepackage{kotex},任何引擎皆可;要把文档字符串也变韩文就加 [hangul]
  • 离不开 pdfLaTeXCJKutf8CJK* 环境;中文用 gbsn/bsmi,韩文用 kotex(内部落到 cjk-ko)。
  • XeLaTeX 下混排多种文字xeCJK 加上每种文字一个 \newCJKfontfamily。不设的话,缺字会悄悄消失。
  • 日文 → 不在本页范围内,请改用 upLaTeX 或 LuaTeX-ja。