简介面向Texlive中文排版的使用者CJKGBK中字体生成器是一套专门解决LaTeX中文字体缺失问题的实用工具包。针对CJK宏包安装后仍无法显示GBK汉字这一常见痛点它基于GBK编码标准打包了宋体、黑体、仿宋、楷书等多款字体的生成与配置方案可让中文字符在编译后的文档中清晰呈现。压缩包整体约289KB共包含10个文件既有可直接运行的exe程序也有makefile、bat构建脚本同时附带C语言源码和ini配置示例便于高级用户按需修改或重新编译。当前已有1471人学习下载非常适合正在搭建LaTeX中文写作环境、撰写学位论文或技术报告的研究者和学生。借助这套工具用户能快速完成GBK字体生成、安装与路径设置与CJK宏包无缝衔接省去四处搜寻字体的繁琐过程明显提升中文排版效率。1. 给TeX Live装CJK宏包卡在GBK字体上这个生成器到底帮你解决了什么用TeX Live写中文文档的人大概率遇到过同一个场景CJK宏包装好了\begin{CJK}{GBK}{gbsn}一写编译直接报错或者PDF里汉字全部变成实心方块。问题往往不在宏包本身而在字体——CJK宏包走GBK编码时需要一套专门为它定制的TFM、VF、FD、ENC和Type1字体文件这套文件不会因为你系统里装了个中文字体就自动生成。所谓“CJKGBK中字体生成器”干的事就是把一个普通TTF/OTF字体批量转换成CJK宏包能识别、能嵌入、能按GBK双字节索引排版的整套字体文件。这篇文章不是讲XeTeX下用系统字体的新方案而是把pdfLaTeXGBK这条老路彻底走通这套文件长什么样、怎么生成、装到哪个目录、踩过哪些坑一次说清楚。2. 拆解GBK字体文件CJK宏包为什么需要TFM、FD和ENC很多人第一次接触CJK字体时以为“把TTF扔进texmf树就完事”结果被一连串 .tfm、.vf、.fd 文件搞到怀疑人生。这些文件不是生成器没事找事而是CJK宏包在pdfLaTeX这条技术路线下的刚性要求。理解它们的分工后面调参数才不是瞎试。2.1 CJK宏包的两个编码路线UTF-8与GBK各自要什么CJK宏包支持多种输入编码最常见的两条是UTF-8和GBK。UTF-8路线下一个汉字在源文件里占三个字节CJK宏包通过utf8.enc把Unicode码位映射到字体的glyphGBK路线下一个汉字占两个字节对应的是GB2312/GBK内码体系映射逻辑完全不同。两条路线需要的文件也不一样。UTF-8有现成的CJK utf8字体配置很多发行版自带就能跑GBK则取决于你有没有为某个字体生成过gbk.enc编码向量下的度量文件。GBK的坑在于它的双字节内码不是简单把Unicode截断CJK宏包内部会把每个汉字当成一个“字符对”处理排版时先按VF虚拟字体找到内码位置再映射到字体文件里的实际字形。路线源文件编码汉字字节数需要的关键文件典型场景UTF-8UTF-83utf8.enc、对应TFM/VF/FD新文档、无历史包袱GBKGBK/GB23122gbk.enc、对应TFM/VF/FD老工程、合作方文件、数据库导出如果你的工程必须用GBK比如甲方给的.tex模板是GBK存盘或者从某老旧系统导出的内容直接是GBK那你就绕不开这套为GBK定制的文件。字库生成器解决的正是这个“定制”过程而不是让你手工一个个去写映射。2.2 一个GBK字体由四类文件组成从TFM到FD的分工一次成功的中文编译背后是一条文件链。我按它们在编译时被读取的顺序来拆TFMTeX Font Metric只存字符的宽度、高度、深度等排版度量不含字形轮廓。pdfLaTeX排版时先用它确定每个汉字占多宽、行怎么断至于字形长什么样TFM不关心。VFVirtual Font是GBK方案里最核心的一环。GBK是双字节内码而TeX传统字体只能按单字节8位寻址最多256个字符位。一个GBK字体要覆盖两万多个码位必须把双字节编码拆解成两层索引。VF的做法是定义一个逻辑字符比如GBK区码位码在VF内部把它展开成对物理字体的多次引用从而打破256字符上限。CJK宏包里形如CJK * gbsn的写法底层调用的就是这个VF。FDFont Definitionc70xxx.fd是LaTeX层面的字体定义文件。它告诉LaTeX某CJK字体家族比如gbsong在C70编码下常规体、粗体、斜体分别对应哪个虚拟字体。文件名的c70就是CJK宏包约定的编码标识。ENCEncoding Vector是编码向量文件。它把字符位置和glyph名字对应起来生成TFM/VF时注入确保映射关系可被dvips或pdfTeX正确理解。GBK方案用的是gbk.enc这份文件CJK宏包里自带不建议自己从头写。MAP与PFB是最后输出PDF时才用到的MAP把逻辑字体名映射到物理字体文件PFBPostScript Type 1二进制字体才是真正嵌入PDF的字形数据。pdfLaTeX不支持直接嵌入TTF所以生成器必须把TTF转成Type1或者生成时让pdfLaTeX能用pk字体临时渲染。这一整套文件就是标题里说的“GBK编码的各种中文字体及相关文件”。生成器的核心工作可以概括成一句话给定一个TTF产出配套的TFM、VF、FD、ENC、MAP和PFB并且统一命名规则让你能在LaTeX源码里用\begin{CJK}{GBK}{gbsong}这种命令直接调用。2.3 pdfLaTeX下字体读取链为什么不能直接装个TTF就完事知道哪些新用户在GBK字体上“翻车”翻得最狠吗就是刚从XeTeX转过来的人。XeTeX可以直接\usepackage{fontspec}调系统字体所以他们认为CJK宏包也能这样。实际上CJK宏包是给传统TeX引擎latex、pdflatex用的传统引擎根本不认识TTF的glyph索引只认TFMVF这套老式度量体系。XeTeX/LuaTeX走的是“系统字体库→fontconfig→直接嵌入”整套字体文件链被引擎内部消化了而pdfLaTeX没有这种能力必须由宏包和外部工具把字体“翻译”成TeX能理解的格式。你可以把生成器理解成一个翻译器TTF是原始素材gbk.enc是翻译规则TFM/VF是翻译后的排版数据PFB是翻译后的字形数据。有一个常见误解需要澄清如果只编译DVI预览其实缺PFB也能看TeX会退回去用pk字体点阵渲染但一旦pdflatex出PDF没有PFB就只能是满屏方块。这也是为什么很多人“latex能看、pdflatex就挂”的原因后面第5章专门说这个。3. 用生成器批量产出GBK字体从TTF到装进TeX Live的完整命令这一章直接进入可复现的步骤。我用的工具都是TeX Live发行版里自带的组件外加一个FontForge做字体格式转换不需要额外下载什么神秘脚本。这套流程我在三台环境上跑过核心命令基本稳定唯一要留意的是工具版本差异。3.1 动手前检查版本、字体源文件和目录规划先确认三件事缺一个后面都会变成玄学问题。第一TeX Live要装全CJK相关组件。很多精简安装没有CJK宏包的完整enc文件后面会报“Cannot find encoding file gbk.enc”。检查命令kpsewhich gbk.enc kpsewhich c70gbsn.fd tlmgr list --only-installed | grep -i cjk如果kpsewhich gbk.enc没有输出路径说明CJK相关包没装全先执行tlmgr install cjk cjk-fonts。这一步解决的是第5章一半的报错问题。第二准备字体源文件。选择一款GBK覆盖较全的开源中文字体格式最好是TTF。OTF里的CFF轮廓经常让老工具链出错如果只有OTF建议先用FontForge转成TTF再继续。把字体文件按“宋体、黑体、楷体、仿宋”四个类别整理到独立目录mkdir -p ~/cjkfonts/{song,hei,kai,fang} # 示例把四个ttf分别放进对应目录 ls -R ~/cjkfonts第三规划安装目录。我强烈建议装到~/texmfTEXMFHOME不要装进/usr/local/texlive/.../texmf-dist。原因很简单TeX Live升级时系统树会被整体覆盖你放进去的自定义文件会被清掉这个“血泪经验”我后面还会提。3.2 核心生成命令从TTF生成TFM和VF以宋体为例生成一个名为gbsong的CJK字体完整命令链如下cd ~/cjkfonts/song # 第一步用fontools的otf2tfm生成TFM和VPL虚拟字体属性列表 otf2tfm -q -v gbsong.vpl -c gbk.enc song.ttf gbsong.tfm # 第二步把VPL编译成VF这才是CJK宏包真正加载的虚拟字体 vptovf gbsong.vpl # 第三步用FontForge把TTF转成Type1字体PFB用于最终嵌入PDF fontforge -script ttf2type1.pe song.ttf gbsong第一条命令里-q是安静模式不打印逐字进度-v gbsong.vpl指定输出虚拟字体属性列表文件-c gbk.enc指定编码向量这一步就是把GBK内码和字体glyph对应关系写进度量数据的关键song.ttf是源字体gbsong.tfm是输出文件名。第二条命令vptovf很好理解VPL是给人看的文本格式VF是给TeX用的二进制格式编译一下而已。如果你用的工具链是cjk-utils里的ttf2tfm它可能直接生成.vf而不用vpl过渡逻辑一样只是少一步。第三步的ttf2type1.pe是一个FontForge脚本内容很简单# ttf2type1.pe Open($1) Generate($2 .pfb) Quit()$1是传入的源字体文件$2是输出前缀最终生成gbsong.pfb。这一步真正解决了“pdfLaTeX能嵌入什么字体”的问题。注意FontForge生成Type1时一定要确保输出字体里的glyph名保留不要选“去掉字形名称”的选项否则后面ENC映射会错位。3.3 安装进texmf树手动拷贝与TDS布局生成出来的文件有gbsong.tfm、gbsong.vf、gbsong.pfb还差两个gbk.enc直接从CJK宏包拷贝和一个手写的c70gbsong.fd。按TeX目录结构TDS放到~/texmf下GT~/texmf mkdir -p $GT/fonts/tfm/cjk-gbk $GT/fonts/vf/cjk-gbk $GT/fonts/type1/cjk-gbk $GT/fonts/enc/cjk-gbk $GT/fonts/map/cjk-gbk $GT/tex/latex/CJK cp gbsong.tfm $GT/fonts/tfm/cjk-gbk/ cp gbsong.vf $GT/fonts/vf/cjk-gbk/ cp gbsong.pfb $GT/fonts/type1/cjk-gbk/ cp $(kpsewhich gbk.enc) $GT/fonts/enc/cjk-gbk/ cp c70gbsong.fd $GT/tex/latex/CJK/ # 刷新文件名数据库让kpsewhich能找到新文件 mktexlsr $GT拷贝逻辑不复杂但目录必须匹配TDS规则TFM进fonts/tfmVF进fonts/vfType1进fonts/type1ENC进fonts/encFD进tex/latex下。mktexlsr $GT是让kpathsea数据库记下这些新文件忘了执行的话后面全是“File not found”。还有一个容易被忽略的步骤MAP文件。如果生成器没有自动输出MAP需要手写一份内容格式大致是gbsong GBSong gbsong.pfb然后把这份gbsong.map放进$GT/fonts/map/cjk-gbk/再执行updmap-user --enable Mapgbsong.mapupdmap-user只作用于当前用户不需要root权限也不影响系统其他用户。启用后可以kpsewhich gbsong.map验证。3.4 三个必调参数编码文件、字体名、粗细映射生成和安装跑通之后真正决定“好不好用”的是下面三个参数它们也是不同生成器之间差异最大的地方。编码文件-c 参数。一定要用CJK宏包自带的gbk.enc或者至少以它为模板。曾经有人为了“优化性能”自己写了精简版ENC结果引号和破折号在PDF里全部错位。GBK的符号区、汉字区、扩展区排列非常密集编码向量错一位整段文字排版就乱了。字体名gbsong/gbsn/gkai。这个名字决定了FD文件名c70gbsong.fd和LaTeX调用名\begin{CJK}{GBK}{gbsong}。CJK宏包老用户约定俗成的命名是类别字体名FD文件说明宋体gbsongc70gbsong.fd默认正文黑体gbsnc70gbsn.fd标题、强调楷体gkaic70gkai.fd引文、批注仿宋gbfangc70gbfang.fd公文风格不要随便发明新名字除非你有意做一个独立字体家族。沿用惯例的好处是老文档里\begin{CJK}{GBK}{gbsn}不用改就能用。粗细映射FD文件内容。中文字体没有真正意义上的“粗体”形态所以FD文件里要把bxbold extended映射到另一个物理字体。最常见的做法是映射到黑体\DeclareFontFamily{C70}{gbsong}{\hyphenchar \font\mne} \DeclareFontShape{C70}{gbsong}{m}{n}{- CJK * gbsong}{} \DeclareFontShape{C70}{gbsong}{bx}{n}{- CJK * gbsn}{}最后一行就是“粗体用黑体”的映射。如果你手里有真正的粗宋体源文件也可以生成一个gbsongb然后映射过去但开源字体里极少有粗宋体所以“粗体映射到黑体”是更通用的做法。斜体通常就保持直立中文排版里斜体用得少强行倾斜反而难看。4. 字体选型与映射哪种开源中文字体适合生成GBK字体生成器本身只是个管道真正决定最终效果的是你塞进去的字体源。这一章说选型因为很多人第一步就选错了字体后面所有参数调优都是白费。4.1 宋体、黑体、楷体、仿宋四类常用字体的GBK覆盖差异同样叫“开源中文字体”覆盖范围差异极大。有的字体只做了GB2312那6763个汉字有的把GBK全字库做进去了还有的连GB18030的扩展区都收。选择标准很简单越全越好。类别常见开源TTF特征GBK覆盖情况使用位置宋体类笔画横细竖粗、有衬线多数只覆盖GB2312少数全GBK正文黑体类笔画均匀、无衬线覆盖普遍较好标题、强调楷体类手写风格、笔画有顿挫覆盖参差不齐需要仔细查引文、批注仿宋类宋体骨架、笔画更细覆盖不全的情况最多公文我的经验是黑体类字体通常覆盖最全因为开源社区做黑体时更注重字库完整性楷体和仿宋因为字形工作量太大很多只做了常用字。如果你必须用楷体或仿宋出正式文档生成之前务必做一次缺字检查。检查缺字不需要等生成完成直接在源TTF上扫一遍就行。用Python的fontTools库写个十几行脚本from fontTools.ttLib import TTFont import sys font TTFont(sys.argv[1]) cmap font.getBestCmap() # 检查Unicode基本汉字区GBK内码映射到这一区间 missing [cp for cp in range(0x4E00, 0x9FA6) if cp not in cmap] print(基本汉字缺失:, len(missing)) # 再检查GBK扩展符号区比如带圈数字、序号 extra [0x2460, 0x2461, 0x3231, 0x3232] missing_extra [hex(cp) for cp in extra if cp not in cmap] print(扩展符号缺失:, missing_extra)getBestCmap()返回字体最优的Unicode到glyph映射表然后按Unicode码位范围扫描。0x4E00到0x9FA5是Unicode基本汉字区GBK收录的汉字基本都落在这里。带圈数字那几项是GBK里很常用但很多字体不做的符号特别值得单独查。如果缺失数超过几十个直接换字体源不要指望生成器帮你补字形。4.2 用FD文件做粗细映射粗体不是换字体是换FD第3章末尾已经写了FD文件里bx映射到黑体的做法这里展开说为什么这样设计。LaTeX的字体选择机制里\bfseries会请求bx形状的字体。对英文字体来说字体文件本身就存在一个加粗版本但对中文字体来说绝大多数宋体TTF只有一个字重没有内置粗体。CJK宏包的FD文件提供了一层“间接层”让你可以决定“粗体”到底用什么。常见做法有三种映射到黑体- CJK * gbsn效果是粗体文字变成黑体视觉对比明显是默认方案。映射到同字体的伪粗体需要生成时额外做一个faked bold把笔画外扩一点生成器如果支持会输出gbsongb但开源工具链支持有限。不处理粗体请求落到一个不存在的FD条目编译直接报错“Font not found”。我一般选第一种原因有两个一是开箱即用只需要保证黑体字体也生成了二是排版效果稳定黑体和宋体混排是中文文档最经典的层级搭配。如果你文档里强调部分特别多可以再生成一个专用黑体字体但没必要为了“伪粗体”去折腾FontForge的加粗参数那是另一个深坑。4.3 缺字与兼容性生成器输出后自查一遍GBK字符表字体源检查过关不代表生成结果就绝对可靠。生成器的转换过程也可能丢字尤其是OTF转TTF、TTF再转Type1这种多步转换glyph名错位是家常便饭。所以我有一个固定习惯生成完PFB之后再写一遍字形检查脚本直接对比PFB里的字符数和源TTF的字符数。# 用FontForge脚本统计字符数 fontforge -script count.pe gbsong.pfbcount.pe内容# count.pe Open($1) print(Open($1).glyph_count) Quit()如果PFB的glyph数比源TTF少了一两百个那多半是转换过程中丢字了。这时候不要继续安装回到源TTF重新转或者换一个更稳定的转换参数。丢字问题最阴险的地方在于编译不报错只是某个汉字在PDF里消失或变成空白这种“黑匣子”式失误查起来最费时间。另外要提醒一句GBK方案里字体覆盖只影响“字体里有没有这个字”跟“LaTeX能不能排版这个字”是两回事。LaTeX这边只要\begin{CJK}{GBK}{...}能编译过输入法打出的GBK字符就会被传递到字形映射层如果最终PDF缺字原因几乎都落在字体源覆盖不足或转换丢字而不是CJK宏包配置错误。5. 避坑GBK字体生成与安装的常见问题排查这一章是从多次实践里筛出来的高频故障每一条我都按“现象→原因→解决”写清楚照着排查能省下大半天时间。5.1 “Cannot find encoding file gbk.enc”八成是CJK组件没装全现象编译时直接报错找不到gbk.enc文件或者生成阶段otf2tfm -c gbk.enc就失败提示无法打开编码文件。原因TeX Live默认安装不一定包含CJK宏包的完整enc目录。gbk.enc由cjk或cjk-fonts包提供精简安装时这两个包可能没配上。另外如果自定义安装到~/texmf但忘了mktexlsrkpathsea数据库没刷新也会报同样的错。解决先确认文件到底在不在用kpsewhich gbk.enc如果没输出执行tlmgr install cjk cjk-fonts如果文件在系统树里但编译还报错检查是不是用了-c但没写全路径改成-c $(kpsewhich gbk.enc)更稳妥。装完别忘了mktexlsr这一步是后悔药执行成本几乎为零但能解决一半的“找不到文件”问题。5.2 DVI正常、PDF变方块只生成了度量没生成可嵌入字体现象latex编译能出DVI预览也正常但pdflatex编译PDF时所有中文变成实心方块或完全空白。原因DVI预览用的是TFM的度量信息加上pk点阵渲染不依赖Type1字体而PDF嵌入必须找到可嵌入的字体文件。如果你生成流程里跳过了FontForge转PFB那一步或者PFB没有正确映射到MAPpdfLaTeX就不知道把哪份字形数据塞进PDF。解决确认gbsong.pfb存在且路径在TDS规则下确认MAP文件内容正确gbsong GBSong gbsong.pfb执行updmap-user --enable Mapgbsong.map后重新编译。如果还不行看编译日志里有没有No file gbsong.map或Font ... not found顺着日志里的字体名往回查MAP。5.3 生僻字和符号失踪字体源覆盖不足不是生成器的锅现象正文常用字全部正常但某些字比如GBK扩展区汉字或带圈数字在PDF里消失。原因源TTF本身的cmap表里就没有这些码位。生成器只是忠实执行“有就映射没有就跳过”的逻辑不会凭空补字形。这不是bug是字体源的字库范围问题。解决换覆盖更全的开源字体然后重新走一遍生成流程。如果只是个别字符缺失又不想为此换字体可以用\CJKfamily或\char单独指定另一个字体来输出那个字但不建议大量这么干排版会乱。最彻底的方案是给pdflatex用的字体换成GB18030全字库字体或者长期迁移到XeTeX/LuaTeX 系统字体路线——不过那就离开本文标题的范畴了。5.4 FD文件冲突改了半个钟头调用的还是旧字体现象自己写了c70gbsong.fd也放进了~/texmf但编译结果和改动前一模一样。原因同名FD文件在多个位置存在LaTeX按kpathsea查找顺序取用了系统树里的旧版你的新文件被“跳过”了另一种可能是改完FD但没跑mktexlsr数据库里还是旧条目。解决先看现在实际加载的是哪个文件kpsewhich c70gbsong.fd如果输出路径不是~/texmf下的那份说明查找顺序不对。把自定义FD放进~/texmf/tex/latex/CJK/并确保TEXMFHOME在查找序列前面默认如此然后mktexlsr刷新。再查一次kpsewhich c70gbsong.fd确认路径变成了你的自定义版本。这个现象在TeX Live里非常典型记住一句话kpsewhich输出哪个路径编译就用哪个文件。5.5 OTF字体生成后字形错位CFF曲线惹的祸现象用一款OTF字体源生成的GBK字体编译不报错但PDF里部分汉字错位或者同一个字在标题和正文里长得不一样。原因很多老工具链包括fontools的部分组件和cjk-utils的早期脚本是按TrueType轮廓glyf表处理的遇到OTF里常见的CFF轮廓提取字形时索引错位轻则个别字错乱重则整套字形错位。生成器若没有明确声明支持CFF就可能踩这个坑。解决先用FontForge把OTF转成TTF再走正常的生成流程。转换命令# otf2ttf.pe Open($1) Generate($2 .ttf) Quit()转换完以后务必用4.3节的检查脚本对比字符数确认没有在格式转换中丢字。这一个动作建议作为所有OTF字体源的固定前置步骤能省掉后面90%的“字形莫名错位”排查。6. 生成结果的验证与批量自动化把GBK字体生成做成一项常规操作字体装好只是开始真正的工程问题是怎么验证、怎么重复执行。我现在的做法是写一个最小验证文档把宋体、黑体、楷体、仿宋四套字体一次性测完再把这个验证过程并进自动化脚本。验证文档的内容要覆盖三类字符常用汉字、全角标点、GBK扩展符号。一个可用的模板\documentclass{article} \usepackage{CJK} \begin{document} \begin{CJK}{GBK}{gbsong} 中文测试的一是在了不和有大这。 标点符号“中文引号”、全角逗号顿号、书名号《》。 GBK扩展符号①②③⑩㈠㈡。 \end{CJK} \begin{CJK}{GBK}{gbsn} 黑体测试标题强调。 \end{CJK} \begin{CJK}{GBK}{gkai} 楷体测试引文批注。 \end{CJK} \begin{CJK}{GBK}{gbfang} 仿宋测试公文风格。 \end{CJK} \end{document}注意这个.tex文件必须以GBK编码存盘不能用UTF-8。很多人在这一步反复“翻车”编译乱码其实是编辑器默认把文件存成了UTF-8。把验证文档和生成命令放进同一个自动化脚本这样每次换字体源或升级TeX Live后跑一遍就能知道整套链条是否健康#!/usr/bin/env bash set -euo pipefail FONT_DIR~/cjkfonts GT~/texmf for spec in song:gbsong hei:gbsn kai:gkai fang:gbfang; do src${spec%%:*} name${spec##*:} otf2tfm -q -v $name.vpl -c gbk.enc $FONT_DIR/$src/$src.ttf $name.tfm vptovf $name.vpl fontforge -script ttf2type1.pe $FONT_DIR/$src/$src.ttf $name cp $name.tfm $GT/fonts/tfm/cjk-gbk/ cp $name.vf $GT/fonts/vf/cjk-gbk/ cp $name.pfb $GT/fonts/type1/cjk-gbk/ done mktexlsr $GT updmap-user --enable Mapgbsong.map脚本逻辑很简单冒号前面是字体源目录名冒号后面是CJK字体名循环里先生成再安装。唯一的隐含假设是每个目录下的 TTF 文件名恰好叫song.ttf、hei.ttf这种实际操作中按自己的文件命名改一行就行。把这个脚本放进版本控制比每次手敲命令可靠得多。最后说一个我自己的教训早期我把生成好的字体直接装进/usr/local/texlive/.../texmf-dist结果TeX Live一次大版本升级整个系统树被覆盖所有自定义字体一夜之间清零。之后我全部改用~/texmfTEXMFHOME再没丢过。如果你也打算长期维护GBK字体请一定把“用户树优先”这件事刻在习惯里——系统树是发行版的领地用户自定义文件放用户树才是正确的生存方式。这套生成器方案值得你投入一次把四套字体跑通后面就是一劳永逸的事。希望帮到你。本文还有配套的精品资源点击获取