简介:这是一份面向TeXLive用户的中文字体生成工具包,专为在LaTeX中使用CJK宏包并处理GBK编码中文文档而设计。资源内含完整的gbkfonts工具及配套文件,可生成宋体、黑体、仿宋、楷书等常用中文字体的GBK映射,解决了GBK编码字符在LaTeX中无法正确显示的问题。包体共10个文件,涵盖可执行的exe程序、C语言源程序、Makefile构建脚本、批处理与配置文件,既可直接运行也可自行编译定制,整个压缩包仅289KB,轻量实用。该资源在CSDN已有1471人学习下载,适合依赖TeXLive进行中文排版的学术作者和编辑者,能够大幅简化字体安装与配置流程,提升中文文档排版效率。
1. CJK 宏包卡在字体上,这套 GBK 字体生成器把路铺平了
先还原一下你大概率见过的场景:TeXLive 装完,ctex 或 CJK 宏包也在,结果编写文档时,编译进程直接停在LaTeX Error: File 'UGBK.fd' not found,后面跟着一长串l.1 \documentclass{article}。中文文档没跑起来,第一道坎就摔在字体上。CJK 宏包的机制和西文完全不同——它不直接调用系统字体,而是需要一套按 GBK 编码表预先切分好的 TFM/OFM 字体文件、CEF 编码文件和 dvipdfmx 用的 map 文件;这些文件 TeXLive 默认不完整,需要自己生成。这套 CJK+GBK 中字体生成器就是干这个的:把 GBK 编码下的常见中文字体批量做成 CJK 宏包能识别的格式,连字体文件、映射配置和相关辅助文件一次提供到位。适合所有仍用latex + dvipdfmx老流程、或者接手老项目必须保留 GBK 编码的人——尤其是那些不想去研究 ttf2tfm、fontforge 脚本、CEF 语法的实用派。
2. 字体生成器的内里逻辑:从 TTF 到 TFM、CEF、map 的完整链路
2.1 为什么 CJK 宏包离不开 CEF 文件
CJK 宏包的中文字体机制,核心是“编码 + 子字体”两层分离。西文字体一个 TFM 就能描述整款字体,而 GBK 字符集包含两万多个汉字,按 94x94 的区位排布,LaTeX 不可能用一个 TFM 文件装下全部字宽信息。它的解决思路是:把一张 GBK 编码表映射到若干“子字体”,每个子字体对应某个区位或某段码位,编译时按需调用。这个映射关系的描述文件就叫 CEF(Chinese Encoding File)。CEF 里写的是“某段 CJK 编码对应哪个字体名、哪个 TFM 文件、要如何按区位偏移取字”。换句话说,CEF 是字体资源和 TeX 排版引擎之间的接口层,没有它,你就算把 TTF 放在眼前,LaTeX 也认不出哪个字符该取哪个字形。
我最早拿到这套生成器时,第一反应是先看它生成了哪些文件:带gbk字样的 .fd 编码文件、按字体名拆分的 .tfm、.ofm 文件、一份 dvipdfmx 用的 .map、还有几份 CEF 文本文件。这个文件组合正好对应 CJK 宏包工作的完整链路:.fd告诉 LaTeX「这会儿用的是哪个字体族、到哪儿找编码定义」;.tfm提供排版时的字宽、高度、斜度信息;.map在 dvipdfmx 阶段把 TeX 字体名映射回真实字体文件。闭环一旦齐全,编译自然就顺了。
2.2 生成器核心流程:宽度提取与 TFM 装配
先看生成器在做什么。它的主要流程可以概括成三步:从 TTF 提取每个汉字的宽度信息、按 GBK 区位切成子字体、生成 TeX 侧需要的描述文件。第一步是宽度提取,这一步可以用 FontForge 脚本批量做,也可以走 ttf2tfm 工具,两者结果一样——拿到每个汉字 glyph 的宽度、高度、深度。第二步是子字体装配,实现上往往是一段 Perl 或 Python 脚本,遍历 GBK 码表,按 32 或 64 个字符一组切开,生成一组 TFM 文件。第三步是资源落地,把生成的 TFM/OFM 写到 texmf 树,并输出 CEF 和 map 文件。下面我用一个简化脚本演示这个思路,方便你理解参数是怎么传的:
# 以 GBK 区间 0xB0A1-0xD7F9 为例,取出该区间内每个码位对应字形宽度 for code in $(seq $((0xB0A1)) $((0xD7F9))); do char=$(printf "\\x%04x" $code) # fontforge 脚本按码位取值,输出该字符的宽度并追加到宽度表 fontforge -quiet -lang=ff -c " Open('/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc'); Select(\$0x${char/\\x/}); Print(GlyphInfo('Width')); " >> gbk_widths.txt done这个脚本的核心是GlyphInfo('Width'),它拿到的是字体设计坐标系里的宽度,单位是 font unit。生成器拿到这套原始宽度后,还要除以 upem(units per em,一般 TTF 是 2048 或 1000),换算成 TeX 侧能用的 scaled point。换算系数写错是生成器最容易翻车的位置之一——如果发现编译出来的中文行尾参差不齐、字与字之间重叠,多半是宽度换算这个环节出了问题。再往后的 TFM 装配,则是按 CEF 里的分段,把同一段码位的宽度表打包成一个 TeX 内部格式的 TFM。这套步骤不需要你每次重跑,生成器已经处理干净了,理解它只是为了后面自定义字体时心里有数。
3. 安装与编译:把生成器素材装进 TeXLive 并跑通第一份文档
3.1 文件布局与 texhash:别把字体文件放错层级
拿到生成器压缩包后,先别急着双击安装脚本。先确认目录结构:一般会有fonts/、tex/、dvipdfmx/三层,分别对应 TFM/OFM 字体文件、.fd 编码定义文件、map 与 CEF 文件。这里最忌讳的是把全部文件扔进一个目录,然后指望系统自己找到。TeX 目录结构(TDS)有严格约定,TFM 文件必须放在fonts/tfm/<供应商>/<字体名>/下,.fd 文件必须放在tex/latex/<宏包>/下,map 文件放在dvipdfmx/或dvips/下。放错层次之后,Kpathsea 搜索就会扑空,表现就是编译时各种找不到文件。
我建议直接并入本地 texmf 树,也就是~/texmf或texmf-local。以 TeXLive 为例,把生成器内fonts、tex、dvipdfmx三个目录按原结构拷贝到texmf-local下。拷贝完成后一定跑一次刷新:
# 刷新文件名数据库,让 kpathsea 能索引到新放入的字体 mktexlsr ~/texmf # 或者全局限定(取决于发行版) texhash ~/texmfmktexlsr和texhash是同一件事的两个名字,前者多出现在 TeXLive 脚本里,后者常见于 MiKTeX 和部分发行版。跑完后可以用kpsewhich验证文件确实进入了搜索路径,这一步很关键,我后面会再强调。验证命令很简单:kpsewhich gbsn00lp.tfm,如果返回完整路径说明索引成功;如果返回空,说明文件根本没落进树里,编译时照样找不到。
3.2 map 文件注册与 dvipdfmx 参数设置
字体文件放进树只是第一步,dvipdfmx 在 PDF 输出阶段还需要一张“字体名 ↔ 真实 TTF/OTF 文件”的对照表,也就是 map 文件。生成器自带的cjk.map里通常长这样:
gbsn00lp NotoSerifCJK-Regular.ttc gbsn00fs NotoSerifCJK-Regular.ttc gkai00mp NotoSerifCJK-Regular.ttc第一列是 TeX 侧字体名,第二列是系统能识别的字体文件名或字体别名。这里有个常见盲区:第二列写的是字体家族名而不是文件路径,dvipdfmx 会通过 fontconfig 去解析这个名字。如果你的系统里字体名是Noto Serif CJK SC,而 map 里写的是NotoSerifCJK-Regular.ttc,那么编译虽然能出 PDF,但中文会变成空白或乱码。
接下来要把这个 map 注册进 dvipdfmx。有两个入口:一是修改dvipdfmx.cfg,二是编译时用-f参数临时指定。修改配置文件得一劳永逸,但会影响全局所有文档;临时指定则只对本命令生效,适合先验证。通常推荐先用后者排查:
# 先用 -f 指定生成器自带的 map,验证配置是否可用 latex chinese-test.tex dvipdfmx -f cjk.map chinese-test.dvi-f参数后面必须跟 map 文件名,且这个文件也得能被 kpathsea 搜到。如果放在当前目录,一般没问题;如果放在texmf-local深处,记得用kpsewhich cjk.map确认。搜索顺序上,Kpathsea 会优先找当前目录,再找 texmf 树,所以临时调试时把 map 放在工作目录最稳妥。
3.3 一个可直接编译的测试文档
装完框架,需要一份能验证全链路的测试文档。下面这份是我常用的模板,覆盖了 CJK 宏包最基本的调用方式:
\documentclass[12pt]{article} % 使用 CJK 宏包的 GBK 编码方式 \usepackage{CJK} \begin{document} \begin{CJK}{GBK}{gbsn} 字体生成器测试:简体中文排版,GBK 编码。 \end{CJK} \end{document}注意\begin{CJK}{GBK}{gbsn}里的三个参数:第一个代表编码方式,必须和 tex 源文件的保存编码一致;第二个GBK是输入编码;第三个gbsn是字体族名,对应生成器里那一组gbsn00lp、gbsn00fs子字体。编译时按latex -> dvipdfmx两步走:
latex chinese-test.tex dvipdfmx -f cjk.map chinese-test.dvi如果两步都无报错,且生成的 PDF 中中文清晰、无方块,说明生成器安装成功。如果报CJK package requires a font family或者Unknown font family 'gbsn',那问题多半出在 .fd 文件没被找到,回到 3.1 检查文件层级。我个人习惯把这一步测试文档和 map 文件放在同一个项目目录里,作为以后排查问题的基线——任何一次“突然编译不过”,先跑这个基线文档,立刻能区分是全局配置被动过,还是当前文档自身的问题。
4. 常见问题排查:GBK 字体翻车现场的四个修复方案
4.1 现象一:编译报UGBK.fd not found
这是最经典的一道坎,经历过 CJK 宏包的人基本都遇到过。现象是 latex 刚开始读入文档就中断,终端提示找不到UGBK.fd文件。原因在于\begin{CJK}{GBK}{gbsn}这行命令会去查找UGBK.fd,这个文件定义了 GBK 编码和字体族之间的绑定关系。生成器虽然生成了这个 .fd 文件,但如果你把它放在了错误的目录层级,或者没有执行mktexlsr,Kpathsea 搜索整个 texmf 树都找不到它。解决办法是先确认文件位置:用kpsewhich UGBK.fd查看返回结果。如果为空,就在 texmf 树里手动查找这个文件,然后把它放入tex/latex/cjk/下并重新mktexlsr。如果文件存在且路径正确但仍报错,检查是不是同时存在多个 TeX 发行版、Kpathsea 索引到了另一棵树的残留文件,这种版本污染问题在折腾过的环境里尤其常见。
4.2 现象二:PDF 里中文全是黑方块或空白
这个现象比上一个更隐蔽,因为 latex 阶段不报错,dvi 也正常,只有打开 PDF 才发现中文全是占位方块。原因通常是两个:要么 dvipdfmx 没加载对应的 map 文件,导致 TeX 字体名无法映射到真实 TTF;要么 map 文件里第二列写的字体名在系统里根本不存在。先检查 map 是否被加载,用dvipdfmx -v -f cjk.map chinese-test.dvi跑一次,-v会输出详细日志,里面会列出每个字体名被映射到哪个文件。如果日志显示gbsn00lp -> Unknown font,那就是字体名写错或字体未安装。此时打开生成的 map 文件,核对第二列是否和系统字体一致,必要时改成fc-list输出的标准家族名。还要注意一点:map 文件里的字体名不能带路径,dvipdfmx 不会解析路径,只认字体别名。
4.3 现象三:UTF-8 工程项目里中文全部乱码
现在很多编辑器默认保存 UTF-8,老项目却用的是 GBK。现象非常明确:tex 文件里中文在编辑器里正常,latex 编译后 PDF 里中文却成了乱码符号。原因是\begin{CJK}{GBK}{gbsn}声明了 GBK 输入编码,而源文件实际保存的是 UTF-8,两者不匹配导致每个汉字被拆成两个字节、逐一解释成错误码位。解决思路有两个:一是把源文件另存为 GBK 编码,满足宏包声明;二是把宏包调用改成\begin{CJK}{UTF8}{gbsn},前提是生成器提供的 .fd 文件同时包含UUTF8.fd定义。大多数生成器素材包里两者都有,你只需要改一个字母。我一般会推荐统一到 UTF-8,因为新版工具链和编辑器对 UTF-8 支持更好,但如果你在维护老项目、其他配套文件都是 GBK,那就反向统一。最忌讳的是混着来——报告说 GBK 的源码、UTF-8 的 .fd、GBK 的宏包参数,三方不一致必然出错。
4.4 现象四:改完 map 或 CEF 后不生效
调试时改怎么样都不生效,重启 latex 也没用。这个通常是缓存和搜索路径的优先级问题。TeX 的文件搜索是靠 kpathsea 数据库完成的,你改了texmf-local里的文件,但数据库里存的还是旧路径,自然读的还是旧文件。更隐蔽的情况是:当前项目目录下也存在同名 map 或 .fd 文件,它的优先级高于 texmf 树,导致你改的是树里的那份、实际读的却是项目里那份。解决办法两条腿走路:先mktexlsr刷新数据库,再检查当前目录是否有同名文件干扰。排查顺序我一般这样走:kpsewhich cjk.map看返回路径,如果路径指向当前目录而你明明改的是 texmf-local 里的文件,立刻把当前目录下的同名文件移走或删除。这个坑在我接过的一个跨平台系统项目里出现过——某开发者把地图文件夹杂在源码目录里交付,结果改 texmf 配置死活不生效,最后发现是源码目录里的旧文件盖过了所有系统配置。
5. 进阶玩法:用自己的 TTF 入库与缺字体检
5.1 用自己的 TTF 批量生成字体族
生成器自带的字体素材未必符合你的审美或项目要求——比如某实验室要求使用指定商用字体,这时候你就得让生成器的流程为自己的 TTF 再走一遍。核心思路不复杂:把字体放进生成脚本的输入目录,修正 map 里的字体名,再重新生成即可。我一般是这样操作的:
# python 脚本:扫描字体目录并生成 dvipdfmx map 片段 import os for f in os.listdir("/usr/share/fonts/custom"): if f.endswith(".ttf"): name = os.path.splitext(f)[0] print(f"{name} {f}")输出结果里每一行就是一个 map 条目,复制到生成器的 cjk.map 末尾,然后按 3.2 的方式重新加载。这里要注意字体家族命名必须和 fc-list 返回的家族名完全一致,比如你用的字体显示为CustomSong-Regular,map 里就不要写成CustomSong,否则 dvipdfmx 依然找不到。生成器自带的脚本在某些发行版下可能因为 FontForge 版本差异报 API 过时,我的习惯是直接改用 Python 调 fontTools 库读取 TTF 宽度信息,生成器和 fontTools 二选一,不必死磕原版脚本。
5.2 缺字验证与参数微调
字体入库只是开始,真正干活时最怕的是某个汉字体在选中字体里压根没有字形。GBK 两万多字,商用字体未必全部覆盖,缺字时 PDF 里会出现占位黑框或空白,而且不会报编译错误。我的做法是生成一份覆盖全部 GBK 常用区的测试文本——按区位顺序排列,编译成 PDF 后重点检查拼音首字母对应的生僻字区域。缺字验证的文本模板可以复用生成器自带的覆盖测试文件,也可以自己拿区位表批量生成。这一步抓出来的漏字,再回去换字体或补字库,比在正式文档里发现黑洞要高效得多。
还有一个经常被忽略的参数:CJK 宏包里的字体缩放。\begin{CJK}{GBK}{gbsn}默认按字体设计尺寸排版,如果发现中文和其他西文基线不对齐,可以在\begin{CJK}环境里加字号调整命令,或者直接修改 .fd 文件里的字体尺寸声明、统一放大或缩小。这个调整不会改变 TFM 里的相对宽度,改的是基准字号,影响全局,适合在字体过大或过小的场景下微调。
从那以后,我每次在 TeX 里动字体配置,都强制自己先跑一遍基线测试文档加一次kpsewhich检查,省掉后面 80% 的拼写级报错排查。希望帮到你。
本文还有配套的精品资源,点击获取