根本原因是pdf生成工具默认不嵌入中文字体且html未声明字体路径,需用@font-face显式指定ttf/woff2字体url、禁用local()、设font-display:swap,并根据工具(wkhtmltopdf/puppeteer)配置访问权限与环境字体。

HTML导出PDF时字体不嵌入导致乱码
导出为PDF时中文显示方块或问号,根本原因不是HTML写错了,而是PDF生成工具(如wkhtmltopdf、WeasyPrint、Puppeteer)默认不嵌入中文字体,且HTML里没声明可用字体路径。
-
@font-face必须显式定义字体文件路径,不能只写系统字体名(如"Microsoft YaHei"),PDF工具通常不访问宿主系统字体库 - 字体文件需是TTF或WOFF2格式,且必须能被CSS加载到——如果是本地导出,用
file:///绝对路径;服务端渲染则要确保字体在Web服务器可访问路径下(如/static/fonts/msyh.ttc) - Chrome DevTools里“Elements → Computed → Font”能看到实际生效的字体,但这个不保证PDF里也生效;得在PDF输出后用Adobe Acrobat的“文件 → 属性 → 字体”确认是否显示“Embedded Subset”
使用@font-face嵌入中文字体的实操要点
光写@font-face不够,容易因路径、格式、声明顺序失效。
- 字体文件建议用TTF(兼容性最好),避免OTF(部分工具解析异常);文件名别含空格或中文,用
msyh-normal.ttf这类命名 - CSS中
src优先用url(),不要用local()——local("微软雅黑")在无GUI环境(如Linux服务器)必然失败 - 必须设置
font-display: swap,否则某些工具(如Puppeteer)会因字体加载超时回退到默认字体 - 示例:
@font-face { font-family: "SimSun"; src: url("/fonts/simsun.ttc") format("truetype"); font-weight: normal; font-style: normal; font-display: swap; }
wkhtmltopdf中文字体乱码的典型配置坑
即使CSS嵌入了字体,wkhtmltopdf仍可能乱码,因为它默认用Qt WebKit渲染,对CSS字体加载支持弱。
- 必须加
--enable-local-file-access参数,否则url()字体路径会被拦截 - 推荐用
--font-outline 1强制轮廓渲染,比位图更稳定(尤其小字号中文) - 避免用
--no-outline或--quiet掩盖字体加载失败日志;加--debug-javascript可看到字体加载是否报404 - 命令示例:
wkhtmltopdf --enable-local-file-access --font-outline 1 --dpi 150 input.html output.pdf
Puppeteer导出PDF时字体未生效的排查点
Puppeteer看似自动处理字体,但实际依赖Chrome启动时的字体上下文,本地开发和Docker环境行为差异极大。
- Docker镜像必须预装中文字体(如Debian系:安装
fonts-wqy-zenhei包),并用--font-render-hinting=none禁用Hinting(避免字形错位) - 页面加载完成后再调用
page.pdf(),但得等字体加载完——加await page.evaluate(() => document.fonts.load("16px SimSun")) - 不要依赖
page.emulateMediaType("screen"),PDF导出用print媒体类型,CSS里要用@media print重置字体栈 - 字体栈写法要保守:
font-family: "SimSun", "Noto Sans CJK SC", sans-serif,避免用"PingFang SC"这类macOS独占字体
url()路径,或者字体文件本身有版权限制(如Windows自带simhei.ttf禁止嵌入),得换开源替代。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











