ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

VS Code 中文 LaTeX 配置:UTF-8、xelatex 与 ctex 排查指南

VS Code 中文 LaTeX 配置:UTF-8、xelatex 与 ctex 排查指南

1. 问题定位:先搞清楚是“编译报错”还是“PDF 不显示”

这个话题看起来像是一个小问题,实际上它包含两类完全不同的故障:一类是编译阶段直接抛出错误,导致 PDF 根本生成不出来;另一类是编译成功了,但 PDF 里的中文要么变成空白,要么变成一排问号、方块,甚至干脆整段消失。这两类问题的排查路径差别很大,混在一起查只会越查越乱,所以我一般先做个简单判断:看终端里有没有报错信息,看输出目录里有没有生成 PDF 文件。

如果生成 PDF 了但中文不对,说明编码和字体链路里某一环没打通;如果连 PDF 都没生成,那说明问题在编译参数、宏包加载或者文件编码层面。我在实际排查中遇到最多的组合是:编辑器本身保存的文件编码不是 UTF-8,同时编译器选择的是传统的pdflatex,再加上中文宏包加载顺序不对这三件事叠在一起。单独看每一件都不致命,合在一起就是“报错 + 不显示”的双重症状。

先把结论摆出来:在 Windows 上使用 VS Code 写中文 LaTeX,最稳妥的组合是UTF-8 编码 +xelatex编译器 +ctex宏包。这套组合能覆盖绝大多数中文排版需求,下面所有的排查和配置都围绕它展开。你如果现在就想动手改,可以先跳到第三节看配置,再回头理解原理;如果已经踩坑了,建议按第一节的顺序走一遍,避免改了半天没改到点上。

需要提前说明的是,本文提到的所有路径、文件名、配置片段都来自通用实践,你在自己机器上要按实际安装位置替换。VS Code 的 LaTeX 插件生态这几年变化不小,LaTeX Workshop是主流选择,下面的配置也都基于它。如果你用的是别的编辑器或别的插件,思路一样,但具体字段名会有差异。

2. 中文不显示的根因拆解:编码、字体、编译链三条线索

2.1 文件编码不是 UTF-8,中文在源头就烂了

这是最隐蔽也最常见的一条。Windows 中文环境下,记事本等工具默认可能保存为 GBK 编码,VS Code 打开这类文件时如果没识别出来,你看到的“正常中文”其实在磁盘上是另一套字节。等你保存、编译,PDF 里自然就是乱码或者空白。更麻烦的是,有时候 VS Code 打开时显示正常,是因为它做了自动猜测,但磁盘上其实还是 GBK,编译时读的是原始字节,编辑器显示和磁盘实际内容不一致,这种“看起来没问题”的假象最容易让人走弯路。

排查方法很直接:看 VS Code 右下角状态栏的编码标识。显示UTF-8才算过关,如果显示GBK、GB2312或者UTF-8 with BOM,都得处理。带 BOM 的 UTF-8 在某些编译器下会在文件开头插入不可见字符,导致首行报错,这个坑我在早期项目里踩过不止一次,报错信息往往指向第一行的\documentclass,看起来莫名其妙。

转换方式:点击右下角编码 → 选择“通过编码保存” → 选UTF-8。转完之后建议把文件关掉重开一次,确认状态栏稳定显示UTF-8。如果你有一批文件要处理,可以用脚本批量转换,但注意别把已经是 UTF-8 的文件再转一遍,容易出 BOM。

注意:转换编码会改写整个文件,操作前先提交一次版本或者备份,尤其是那些已经写了几百行的稿子。

2.2 编译器选错,中文宏包根本没有生效环境

传统pdflatex对中文的支持是通过CJK宏包这类方案实现的,配置繁琐、字体支持有限,遇到稍复杂的中文排版就容易出问题。xelatex从设计上就支持 Unicode 和系统字体,配合ctex宏包是现在的主流做法。很多“中文不显示”的案例,追到底就是编译器还停留在pdflatex。

判断当前用的是哪个编译器:看 VS Code 输出面板里LaTeX Workshop打印的编译命令,会明确写出pdflatex还是xelatex。如果你没做任何配置,插件默认会用latexmk走一遍,而latexmk的默认规则往往落到pdflatex上。这就是为什么很多人“什么都没改,就是不显示中文”。

这里有个容易混淆的点:latexmk本身不是编译器,它是一个自动化调度工具,负责按依赖关系多次调用真正的编译器。所以要改的不是latexmk,而是让它调用的引擎变成xelatex。理解这一层,配置的时候就不会乱改。

2.3 宏包加载顺序和参数不对,字体找不到

ctex宏包负责中文排版的一系列底层设置,包括字体选择、行距、标点处理。它加载时会去系统里找中文字体,找不到就报错或者静默降级。常见的错误信息是字体名找不到,或者提示某个字体文件缺失。Windows 上一般用SimSun(宋体)、SimHei(黑体)、KaiTi(楷体)这些系统自带字体,正常安装的系统都有,但也存在被精简版系统裁掉的情况。

加载ctex有两种方式:用\documentclass[UTF8]{ctexart}这样的文档类,或者在标准文档类里\usepackage[UTF8]{ctex}。前者更省事,后者更灵活。两种方式都需要保证参数UTF8和实际文件编码一致,写错了同样会出问题。我一般推荐直接使用ctexart、ctexbook这类文档类,少一层配置就少一个出错点。

另一个细节是字体集参数。ctex支持fontset=windows、fontset=mac、fontset=ubuntu等选项,Windows 上默认就会走windows,一般不用手动指定。但如果你在别人机器上拷来的模板里看到fontset=mac,那在 Windows 上必然出问题,这时候要么删掉这个参数,要么改成windows。

3. 从零配置一套能跑中文的环境:VS Code + LaTeX Workshop 实操

3.1 编译链路的选择逻辑:为什么是 xelatex 而不是 pdflatex

先说清楚取舍。pdflatex的优势是编译速度快、生态成熟、很多老模板只兼容它;劣势是对 Unicode 和系统字体支持差,中文要用CJK方案,配置复杂且排版效果一般。xelatex直接支持 Unicode,能调用系统字体,中文排版质量明显更好,缺点是编译稍慢,极少数老宏包不兼容。

对于写中文文档这个场景,xelatex的优势是压倒性的。我实测过同一份带中文的文档,pdflatex下要么报错要么字体丑陋,换xelatex后一次通过。所以除非你维护的是必须兼容老引擎的模板,否则没有理由不用xelatex。

latexmk在这里的角色是“自动挡”:它会根据文件里是否引用了参考文献、是否有索引、是否需要多次编译才能稳定的交叉引用,自动决定跑几遍。手动敲xelatex两三次也能达到同样效果,但容易漏。所以配置思路是:让latexmk用xelatex作为底层引擎,一次性解决“编译次数”和“引擎选择”两个问题。

3.2 settings.json 的关键字段逐项说明

VS Code 的 LaTeX Workshop 配置都在settings.json里。打开方式:Ctrl+Shift+P→ 输入Open Settings (JSON)。下面这份配置是我目前用的,覆盖中文编译和常用辅助功能:

{ "latex-workshop.latex.tools": [ { "name": "xelatex", "command": "xelatex", "args": [ "-synctex=1", "-interaction=nonstopmode", "-file-line-error", "%DOC%" ] }, { "name": "latexmk-xelatex", "command": "latexmk", "args": [ "-synctex=1", "-interaction=nonstopmode", "-file-line-error", "-xelatex", "%DOC%" ] } ], "latex-workshop.latex.recipes": [ { "name": "xelatex", "tools": ["xelatex"] }, { "name": "latexmk (xelatex)", "tools": ["latexmk-xelatex"] } ], "latex-workshop.latex.recipe.default": "first" }

逐个字段解释。tools定义可用的编译工具,每个工具是一条完整命令。-synctex=1生成同步文件,支持 PDF 和源码之间双向跳转,写长文档时非常有用。-interaction=nonstopmode让编译遇到错误不中断等待输入,否则在编辑器里会卡住。-file-line-error让报错信息带上文件名和行号,排错效率提升明显,强烈建议保留。

%DOC%是占位符,会被替换成当前主文件名(不含扩展名)。注意是主文件,如果你的项目有多个.tex文件,要在主文件上执行编译。

recipes定义编译配方,一个配方可以串多个工具,按顺序执行。这里给了两个配方:单次xelatex和latexmk驱动xelatex。日常用后者更省心,它会自动处理交叉引用需要的多轮编译。

recipe.default设成first表示默认用第一个配方。如果你想默认走latexmk,把配方顺序调换,或者把值改成配方名。我个人的习惯是日常用latexmk (xelatex),因为文档里一旦有目录、参考文献、交叉引用,单次编译的结果是不稳定的,需要多轮才能收敛。

还有一个常被忽略的点:latex-workshop.latex.outDir。默认输出在源文件同目录,会生成一堆.aux、.log、.synctex.gz文件,把目录搞得很乱。可以设成%DIR%/build集中管理,但要注意主文件的相对路径引用(比如插图路径)可能需要相应调整,这一点后面会细说。

3.3 一份最小可运行的中文模板

配置改完,用下面这份最小模板验证环境是否打通:

\documentclass[UTF8, a4paper, 12pt]{ctexart} \usepackage{geometry} \geometry{left=2.5cm, right=2.5cm, top=2.5cm, bottom=2.5cm} \usepackage{graphicx} \usepackage{amsmath} \title{中文排版测试文档} \author{测试} \date{\today} \begin{document} \maketitle \section{中文标题测试} 这是一段中文正文,用来验证字体是否正常加载、标点是否正常显示。 公式测试:$E = mc^2$。 \end{document}

几个关键点。\documentclass的UTF8参数要和文件实际编码一致。ctexart自带中文支持,不需要再额外加载ctex。a4paper和字号放在文档类选项里,比后面用宏包调更规范。中文段落里混排英文和公式是常态,ctex会自动处理中西文间距,不用手动加空格。

编译后如果 PDF 里中文正常,说明链路通到底了。如果标题、正文都正常但某一处异常,比如数学公式里的中文、表格里的中文、图表标题里的中文,那就是局部问题,排查范围可以大幅缩小。

4. 常见报错逐条拆解与现场处置

4.1 字体找不到:从报错信息反推缺什么

典型报错长这样:Font "SimSun" not found或者The font "..." cannot be found。出现这类信息,说明xelatex在系统字体目录里没找到指定字体。先确认报错里点名的是哪个字体,然后去系统字体设置里找。

处置思路分三层。第一层,确认字体是否真的存在。打开系统字体目录,搜一下报错里的名字。第二层,如果字体存在但xelatex找不到,可能是字体缓存问题,重装或刷新字体缓存后再试。第三层,如果字体确实缺失(精简系统常见),要么从别处拷一份字体文件装进来,要么改用系统里已有的字体,在文档类选项里显式指定。

显式指定字体的写法:

\documentclass[UTF8, fontset=windows]{ctexart}

如果windows字体集里某个字体缺失,可以单独覆盖:

\setCJKmainfont{SimSun}

但要注意,单独覆盖只影响主字体,标题、无衬线等字体可能还是走默认,报错可能依然存在。所以最稳的做法是补齐系统字体,而不是逐个覆盖。

注意:字体名区分大小写和写法,SimSun和simsun在不同环境下表现可能不同,尽量按系统里显示的准确名称写。

4.2 编译卡住不结束:nonstopmode 的必要性

不带-interaction=nonstopmode时,编译遇到错误会停下来等用户输入,在 VS Code 这种非交互环境里表现为“一直转圈”。很多人以为是编辑器卡了,其实是编译器在等一个永远不会到来的按键。加上这个参数后,编译遇到错误会继续往下跑,把所有错误一次性打印出来,排查效率高得多。

这个参数也有副作用:它会让编译“带病运行”,即使有错误也可能生成一个残缺的 PDF。所以看到 PDF 生成了不要高兴太早,一定要检查输出面板里有没有错误。我一般把“编译成功”定义得更严格:输出面板无报错且 PDF 页数与预期一致。

4.3 中文变成方块或问号:字体链路断在哪

方块和问号是两种不同的失败。方块通常意味着字体被找到了但缺少对应字形,问号通常意味着编码转换失败,字符在进入排版引擎前就已经丢失。区分这一点能快速定位。

如果是方块,重点查字体。某些字体只覆盖部分汉字,生僻字、繁体字、特殊符号可能缺失。解决办法是换一个覆盖更全的字体,或者对特殊字符单独指定字体。如果是问号或整段中文消失,重点查编码:文件是不是 UTF-8、宏包参数是不是写了UTF8、编译器是不是xelatex。

还有一种特殊情况:PDF 阅读器自带的字体替换。某些阅读器在字体嵌入不完整时会用自己的字体渲染,导致显示和实际不一致。用另一个阅读器打开对比一下,能排除这类干扰。

4.4 只在某个文件出问题:排除法定位

如果同一份配置下,A 文件正常,B 文件不正常,问题基本锁定在 B 文件本身。排查顺序:文件编码 → 文档类参数 → 是否有特殊宏包 → 是否有从别处拷来的片段。

我从别处拷代码片段踩过一次坑:拷来的片段里带了不可见字符,编译报错指向的行看起来完全正常。这种问题用编辑器的“显示所有字符”功能能看到异常符号,肉眼看不出来。所以从网页、PDF、聊天窗口拷内容进.tex文件,最好过一遍纯文本编辑器,或者拷完立刻编译一次,别攒着。

5. 提高效率的辅助配置与使用习惯

5.1 编译触发方式:保存自动编译与手动编译的取舍

latex-workshop.latex.autoBuild.run控制自动编译行为。可选值有never、onSave、onFileChange。onFileChange最激进,每次改动都触发,写长文档时频繁编译会拖慢编辑器。onSave是折中方案,保存时编译一次,节奏刚好。我习惯用onSave,配合手动快捷键补编译。

{ "latex-workshop.latex.autoBuild.run": "onSave" }

写大文档时,一次完整编译可能要几秒到十几秒,自动编译太频繁会打断思路。这时候可以临时切成never,需要看效果时手动触发。

5.2 清理中间文件:别让垃圾文件干扰下一次编译

.aux、.log、.out、.toc、.synctex.gz这些中间文件在正常情况下会自动更新,但遇到编译中断、手动改过文件、切换编译器时,残留的旧文件可能导致诡异问题,比如目录不更新、交叉引用错乱。清理一遍再编译,往往能解决“代码没改但结果不对”的怪现象。

LaTeX Workshop 提供了清理命令:Ctrl+Shift+P→LaTeX Workshop: Clean up auxiliary files。养成在切换编译器、切换文档类之后清理一次的习惯,能省掉不少无谓排查。

5.3 正反向搜索:写长文档的必备能力

-synctex=1生成的同步文件支持双向跳转。正向是从源码跳到 PDF 对应位置,反向是从 PDF 点击跳回源码。VS Code 里Ctrl+Alt+J正向,Ctrl+ 点击 PDF 反向。写几十页的文档,没有这个功能定位起来非常痛苦。

这个功能依赖 PDF 阅读器支持。VS Code 内置的 PDF 预览支持反向搜索,用外部阅读器就不一定了。所以建议先用内置预览验证功能是否正常,再决定用哪个阅读器。

6. 那些文档里不写的实操心得

先讲一个我踩过的坑。早些年我习惯把所有配置写在一个settings.json里,后来换机器、换项目,配置冲突搞得焦头烂额。后来改成工作区级配置(项目根目录下的.vscode/settings.json),全局配置只保留通用的部分。这样每个项目可以有自己的编译配方,互不干扰。尤其是维护多个模板时,这个习惯能救命。

第二个心得关于编码转换。批量转换文件编码时,不要一次性全转完再编译,而应该转一个编译一个。原因是某些文件里可能有特殊字符,转换过程会暴露问题,一次性全转完你就不知道是哪个文件出的事。小步快跑,出问题好定位。

第三个心得关于字体。如果你的文档里要处理生僻字、古籍用字、特殊符号,系统自带字体很可能覆盖不全。这时候提前确认字符覆盖范围,比写到一半发现显示不出来要省事得多。可以先用一个小文档把所有需要的字符跑一遍,确认无误再大规模写。

第四个心得关于模板复用。从网络下载的模板,先在一个空目录里单独编译一遍,确认能在本机跑通,再往里面填内容。直接往模板里灌内容,一旦出问题,你就分不清是模板的问题还是内容的问题。

最后说一个判断原则:中文 LaTeX 的问题,九成以上落在“编码、引擎、字体”三件事上。遇到报错或者不显示,别急着改宏包、改参数,先按这个顺序过一遍。编码看状态栏,引擎看输出面板的命令行,字体看报错信息和系统字体列表。这三处都确认无误,问题基本就解掉了。那些看起来复杂的报错,往往只是这三条链路里某一环没对齐。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进