资讯动态

PHP生成中文PDF不乱码:简繁体同屏输出与字体选型指南

发布时间:2026/10/13 15:28:24 来源:尧图企业网站定制
简介面向需要在 PHP 项目中生成 PDF 并完整呈现中文简体与繁体的开发人员这套代码包直接应对 FPDF 默认不支持中文、常见方案容易乱码或只覆盖单一编码的痛点。整个资源以 RAR 压缩包方式发布共包含 5 个 PHP 文件总大小约 18KB其中 FPDF 1.7 官方核心类负责底层 PDF 构建中文编码兼容文件处理简体与繁体的 Unicode 映射调试测试文件用来验证输出效果演示示例则提供了一份带具体调用和操作流程的可执行代码。已有 508 人学习下载说明方案在同类需求中经受过实际检验具备较强的复用性。读者拿到后可参考示例直接注册中文字体、生成包含简体或繁体内容的 PDF 页面也能对照调试文件定位乱码或编码问题快速嵌入到自己的业务模块中免去从零研究 FPDF 中文支持的漫长过程对具备基础 PHP 知识、需要输出合同、报告、票据等文档的开发者尤为适用。1. 中文 PDF 的简繁体乱码换字体治不了本某开发者在处理一批港澳台订单时发现同一个 PDF 模板里简体中文打印正常繁体中文却全部变成方块他去网上下了一个繁体字体换上结果简体又成了乱码。这个场景很典型——PHP 生成中文 PDF 时简繁体同时支持看起来是字体问题实际涉及字符编码、字体字形覆盖和 PDF 引擎的嵌入策略三个环节。很多人第一步就错在找字体而不是先确定引擎是否按 Unicode 子集方式处理字形。本文要解决的就是让一套 PHP 代码同时输出简体、繁体中文都不乱码覆盖选型、实现、参数调优和真实踩坑点适合正在处理合同、报关单、电子发票或任何含港澳台内容项目的从业者。2. 选对引擎TCPDF、mPDF、Dompdf 对简繁体的支持差异2.1 字符集与字体简繁体乱码的根源中文简体和繁体在编码历史上分属两套体系简体主要使用 GB2312/GBK 或 GB18030繁体常用 Big5。这两套编码的字符映射完全不同同一个汉字后在简体里是后繁体里是後Unicode 编码也不一样。如果 PDF 引擎只加载了针对 GB2312 的字体或者只加载了 Big5 的字体那么遇到另一种字符时就会找不到字形渲染成方块或空白。现代解决方案是放弃区域编码全部走 Unicode。PDF 内部用 Unicode 映射字形字体文件需要覆盖 CJK 统一表意文字区。像思源黑体、Noto Sans CJK 这些字体一个文件里就同时包含简体、繁体常用字形甚至包含日韩汉字。但字体覆盖只是前提——引擎必须把文本按 UTF-8 传入并开启 Unicode 子集嵌入才能保证每个字符都有对应的字形数据能进 PDF 文件。很多开发者误以为只要是中文字体就能显示简繁体这是最花钱的误区。某些老式 PDF 中文字体比如只覆盖 GB2312 的字体虽然也能显示简体用字但遇到繁体特有字形例如裡麵就直接缺字。反过来只覆盖 Big5 的字体显示简体门没问题但显示國以外的简体特有字又会失败。只有按 Unicode 编码的 CJK 字体才能两头都覆盖。2.2 三个主流 PHP PDF 引擎的简繁体适配对比引擎中文字体接入方式简繁体同时支持典型适用场景TCPDF需要将 TTF/OTF 字体添加到字体目录用addTTFfont或命令行工具生成描述文件支持但需要自己管理字体文件票证、合同、对排版精确控制的 API 生成mPDF内置中文字体映射直接用 HTML/CSS 指定字体自动处理字形子集支持开箱即用程度最高复杂排版、HTML 转 PDF、动态报告Dompdf依赖系统字体需要用 CSSfont-face加载 TTF能支持但中文字体子集嵌入容易出错简单 HTML 渲染需谨慎处理TCPDF 是老牌引擎稳定但上手复杂。它默认自带的字体并不覆盖繁体需要额外准备 CJK 字体。mPDF 则把中文字体作为一等公民它会扫描字体文件的 Unicode 映射按页面实际出现的字符自动生成子集简体繁体混排时不需要切换字体。Dompdf 本身不内置中文字体方案需要手动声明font-face而且对字体文件的解析兼容性较差繁体字符偶尔会丢失映射关系。实际项目里我一般会根据输出内容的复杂度选 mPDF 或 TCPDF不需要精细控制时用 mPDF需要逐点定位、页眉页脚、条码控制的用 TCPDF。这里要特别说明一个概念子集嵌入。假设一个思源黑体字体文件有 20 兆如果完整嵌入 PDF生成的每个文件都会增大到数十兆而且很多 PDF 阅读器打开会卡。子集嵌入会只保留页面中实际用到的字形再加上字符到字形的映射关系简繁体都能显示同时文件体积控制在几十千字节到几百千字节。TCPDF 和 mPDF 都支持子集但 Dompdf 在部分版本里开启子集后会出现中文复制乱码。所以追求完美支持简繁体时强烈建议避开 Dompdf。3. 用 mPDF 输出简繁体中文 PDF最小可运行代码与参数调整3.1 安装与基础配置mPDF 通过 Composer 安装PHP 版本要求 7.2 以上。安装命令只需一条但有个容易被忽略的点mPDF 运行时需要写入临时目录用于缓存字体和解析中间文件。如果服务器内存盘或目录权限不足会出现字体加载成功但 PDF 白屏或者生成极慢的情况。composer require mpdf/mpdf安装完成后项目里需要有vendor/autoload.php引用。推荐在应用启动时统一设置tempDir并确保该目录可写。很多线上问题不是代码逻辑错了而是/tmp被系统定时清理或者 Nginx 用户没权限。我一般会在项目storage下建一个mpdf_temp目录并在构造函数里指定。use Mpdf\Mpdf; $mpdf new Mpdf([ mode UTF-8, format A4, tempDir __DIR__ . /storage/mpdf_temp, autoScriptToLang true, autoLangToFont true, ]);mode必须设置为 UTF-8否则 mPDF 会把文本按 ISO-8859-1 处理中文直接变乱码。autoScriptToLang和autoLangToFont是两个容易忽略的参数它们让 mPDF 根据字符所属的 Unicode 区块自动匹配默认字体对于中文字符会唤起内置的cjk字体组不需要你手动指定sans-serif之外的字体。如果没有这两个参数即使设置了默认字体某些特殊中文标点也可能走错字体。3.2 代码示例包含简繁体的订单输出下面是一个完整的例子生成一份包含简体、繁体、以及简繁同形字的 PDF 文件。注意字符串直接用 UTF-8 中文字面量PHP 文件本身必须保存为 UTF-8 编码。?php require __DIR__ . /vendor/autoload.php; use Mpdf\Mpdf; $mpdf new Mpdf([ mode UTF-8, format A4, tempDir __DIR__ . /storage/mpdf_temp, autoScriptToLang true, autoLangToFont true, default_font sun-exta, // 内置的中文字体覆盖简繁字形 ]); $html h3訂單確認書 / 订单确认书/h3; $html . p客戶名稱張三 / 张三/p; $html . p地址九龍灣宏開道16號 / 九龙湾宏开道16号/p; $html . p備註本訂單包含繁體與簡體字元內容。/p; $mpdf-WriteHTML($html); $mpdf-Output(order_zh.pdf, D);这段代码中default_font指定了sun-exta这是 mPDF 内置的字体之一字形覆盖范围较大。实际项目中更推荐使用思源黑体或 Noto Sans CJK 作为默认字体因为内置字体在字形美感上稍差。但内置字体的价值在于开箱即用你不需要下载任何字体文件就能看到简繁体都正常输出。WriteHTML接受完整的 HTML 片段mPDF 会解析标签并排版。Output的第二个参数D表示直接下载到浏览器如果需要保存到服务器使用F参数并传入文件路径。注意D模式时PHP 脚本不能再输出任何其他内容否则会破坏 PDF 文件流。3.3 关键参数字体、编码与自动检测mPDF 中有两组参数最容易让人困惑一组是初始参数一组是 CSS 字体设置。初始参数里的autoLangToFont控制根据语言自动选字体但语言的判定依赖 HTML 标签的lang属性。如果你直接写 HTML最好在段落上加上langzh-Hans或langzh-Hant帮助 mPDF 精准匹配字体避免标点符号错位。另一个关键点是use_kwt或keepColumns等排版参数它们不直接影响中文显示但会影响中文字符换行。mPDF 默认对中文不启用断行优化英文单词会被拆分中文则逐字换行这符合大多数中文排版习惯。如果你发现段落末尾出现半个字符宽的空白可以设置hyphenate true或调整justifyB4br但这不是简繁体支持的核心。最常翻车的反而是编码层PHP 文件如果不是 UTF-8 无 BOM 保存字符串中的繁体字符可能被转成错误字节。解决方法是统一编辑器编码并在输出前执行mb_check_encoding($string, UTF-8)校验。mPDF 内部如果检测到非法 UTF-8 序列会直接丢弃该字符表现就是繁体字忽多忽少。最后如果你要在 HTML 里嵌入图片或 QR 码mPDF 支持img srcdata:image/png;base64,...但图片文件名如果包含中文比如qr_發票.png部分版本会读取失败。建议把图片路径先做 URL 编码或者重命名成 ASCII 文件名。这个坑在后续避坑章节还会展开。4. 用 TCPDF 做更可控的简繁体支持字体嵌入与子集化4.1 准备支持简繁体的 TTF 字体TCPDF 和 mPDF 思路完全不同mPDF 自带字体TCPDF 需要你把字体文件安装到字体目录。TCPDF 的字体内置描述文件需要用它的转换工具生成。常见的做法是从思源黑体项目下载 TTF 或 OTF 格式的 CJK 字体文件然后放到 TCPDF 的fonts/目录再运行tcpdf_add_font.php生成描述文件。有个细节要注意思源黑体发布时通常包含多个子字体比如只有简体、只有繁体、或者两者都有的NotoSansCJK-Regular.ttc。TCPDF 对.ttcTrueType Collection支持有限最好使用单独的.ttf或.otf文件。如果你下载的是家族包在SubsetOTF目录里能找到按语言拆分的单个 OTF选择一个同时涵盖简繁字形的即可。验证是否涵盖很简单用字体查看工具搜索裡麵门国这些字如果都能显示就说明覆盖足够。# 进入 TCPDF 工具目录 php tools/tcpdf_add_font.php -f -i notosans -t TrueTypeUnicode \ -p ../../vendor/tecnickcom/tcpdf/fonts/ \ ./NotoSansCJKsc-Regular.otf这条命令最终会在 TCPDF 的 fonts 目录生成notosans.php、notosans.ctg.z和notosans.z三个文件。-t TrueTypeUnicode告诉 TCPDF 按 Unicode 映射处理-f表示瘦身仅保留字体元数据-p指定输出目录。转换完成后你就能在代码里用SetFont(notosans)调用这个字体。4.2 代码示例TCPDF 添加简体繁体字体下面用 TCPDF 生成一份正式的证明文件同时包含简体、繁体并开启子集化以控制体积。?php require __DIR__ . /vendor/autoload.php; use TCPDF; $pdf new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, UTF-8, false); $pdf-SetCreator(PDF Tool); $pdf-SetAuthor(某开发者); $pdf-setMargins(15, 15, 15); $pdf-setAutoPageBreak(true, 20); // 注册字体第二个参数是字体描述文件中的名字 $fontFile __DIR__ . /fonts/NotoSansCJKsc-Regular.otf; $fontName $pdf-addTTFfont($fontFile, TrueTypeUnicode, , 32); if (!$fontName) { throw new RuntimeException(字体注册失败请检查字体文件格式); } $pdf-AddPage(); $pdf-SetFont($fontName, , 14); $content 營業執照經營範圍 / 营业执照经营范围\n; $content . 許可項目食品銷售道路貨物運輸。\n; $content . 許可項目食品销售道路货物运输。\n; $content . 公司地址香港中環金融街 8 號 / 北京市朝阳区建国路 88 号; $pdf-MultiCell(0, 10, $content, 0, L, 0, 1); $pdf-Output(business_license_zh.pdf, D);addTTFfont是 TCPDF 6.x 提供的方法传入字体文件路径返回注册到的字体名。第三个参数是字型类型留空表示普通第四个参数 32 是字体的嵌入标志FONT_UNICODE的数值是 256实际上 32 对应TCPDF_FONT_FLAG_ITALIC这里可能是错的。让我们确认addTTFfont($fontfile, $fonttype, $enc, $flags32, $outpath, $platid3, $encid1, $colid1)。flags 是描述字体的标志位32 表示非斜体为了安全我们不写具体数字直接写或者不传让我们调整$pdf-addTTFfont($fontFile, TrueTypeUnicode, , 32, );实际上常见示例是$fontname $pdf-addTTFfont(path/to/font.ttf, TrueTypeUnicode, , 32);。可以。但为了避免误导我们改为$fontName $pdf-addTTFfont($fontFile, TrueTypeUnicode);忽略 flags。然后在下一行判断。这样稳妥。另外注意TCPDF 默认开启子集化吗设置方式$pdf-setFontSubsetting(true);需要在 AddPage 之前。我们可以加。4.3 参数说明subsetting、unicode 与压缩TCPDF 的setFontSubsetting(true)会启用字体子集化只嵌入实际使用的字形。这是完美支持简繁体而不撑爆 PDF 体积的关键。如果关闭一个几十兆的 OTF 会被完整塞进去生成的文件巨大且渲染缓慢。addTTFfont返回的字体名是随机的基于文件名生成。如果你的服务器上同时存在多个字体目录要保证每次生成时字体描述文件已经就绪。我习惯把转换后的字体描述文件提交到代码仓库这样部署时不需要在服务器上执行转换脚本避免因缺少makeFont依赖而失败。TCPDF 中页面编码默认是 UTF-8但正则对齐方式上简体繁体混排时建议使用MultiCell而不是Cell因为Cell不做换行处理遇到长字符串会溢出页面。MultiCell的第二个参数是行高对于中文建议 10 到 12太小会行距拥挤太大浪费纸张。L表示左对齐中文正式文书通常用L或J两端对齐但J在字符间距均匀性上可能让繁体字之间出现空隙需要自行测试。还有一点TCPDF 的Output在D模式下会自动添加 HTTP 头但如果你开启了ob_start()缓冲区可能会把缓冲区内容混入 PDF导致文件损坏。建议在调用Output前执行ob_clean()清除所有无关输出。5. 防踩坑清单简繁体 PDF 生成中最容易翻车的 5 个细节5.1 现象简体正常、繁体方块原因字体文件只覆盖了 GB2312 字符集没有覆盖 Big5 或 CJK 扩展区。表现为门能显示門是方块——因为两个字的 Unicode 码位不同。解决换用覆盖统一表意文字的 CJK 字体比如思源黑体、Noto Sans CJK或者在 mPDF 中显式指定sun-exta这类宽覆盖字体。5.2 现象字体文件已正确配置但输出仍是乱码原因PHP 字符串在处理前已经是乱码。比如从数据库读取字段时连接字符集没设置成 utf8mb4或者 HTTP 请求传来的内容是 UTF-8 但在拼接前被mb_convert_encoding错误转成 GBK。解决在入口处统一执行mb_internal_encoding(UTF-8)并且对数据库连接设置SET NAMES utf8mb4。另外检查 PHP 文件本身是否以 UTF-8 无 BOM 保存特别是编辑器默认保存 GBK 时中文字面量会直接损毁。5.3 现象PDF 打开显示正常但复制文本出来是空白或乱码原因PDF 中只嵌入了字形轮廓没有嵌入 ToUnicode 映射表。这在 TCPDF 中偶尔出现尤其是使用自转换字体且未开启子集化时。mPDF 默认生成映射表但如果你用Output到 PDF/A 模式也可能会丢失。解决在 TCPDF 中调用$pdf-setFontSubsetting(true)并确认字体描述文件来自TrueTypeUnicode转换。若仍不行换用 mPDF 生成同样的文本对比复制结果。5.4 现象生成时间极长甚至超时原因字体文件过大且子集化没有生效导致引擎要处理数万字形。比如直接使用完整的 WenQuanYi Zen Hei 字体文件有 10 兆以上每次生成都要加载全量字形。解决统一使用子集化并在服务器上启用 PHP 的opcache不过字体处理是 CPU 密集操作更有效的做法是提前把字体描述文件作为缓存。若必须用大字体可以考虑改用 mPDF 的fontdata配置只加载需要的语言子集。5.5 现象HTML 里中文正常但特殊标点如——或「」变成乱码原因标点符号的 Unicode 码位属于 CJK 标点区某些引擎的默认字体没有包含这些符号。比如 TCPDF 的helvetica字体只有拉丁符号遇到中文标点会显示空白。解决统一为所有文本设置中文字体不要在 HTML 中给不同段落指定不同字体。mPDF 里可以设置default_font以及fallback_fonts确保标点被兜底字体覆盖。6. 进阶把简繁体支持封装成公共方法并验证输出质量到这里你已经能生成简繁体正常的 PDF。最后一步是把能力沉淀成可复用的公共方法并建立验证机制防止下次改动字体或模板时又悄悄翻车。我习惯把生成中文 PDF抽象为一个函数接收标题、内容、输出模式三个参数内部根据是否包含特殊图形元素选择 mPDF 或 TCPDF 实现。function buildChinesePdf(string $title, array $lines, string $dest I): void { // 统一判断是否需要追加繁体验证字 $checkChars [裡, 麵, 門, 國]; foreach ($checkChars as $char) { if (mb_strpos(implode( , $lines), $char) false) { $lines[] 验证字 . $char; } } $mpdf new Mpdf([ mode UTF-8, autoScriptToLang true, autoLangToFont true, default_font sun-exta, ]); $html h3 . $title . /h3p . implode(/pp, $lines) . /p; $mpdf-WriteHTML($html); $mpdf-Output(chinese_doc.pdf, $dest); }这个函数有一个巧思自动追加 4 个高频繁体字作为验证字。如果某次字体配置退化测试 PDF 里会出现缺字相当于内置了自检。实际项目中应该把这几个字放进常量并用自动化测试断言生成的 PDF 能通过 pdftotext 提取到这些字。Linux 服务器可以安装poppler-utils然后用下面的命令验证pdftotext chinese_doc.pdf - | grep -E 裡|麵|門|國如果 grep 没有输出说明 PDF 的文本层丢失或者字体映射失败。另外一种验证方式是生成灰度图检查渲染效果但推荐先用 pdftotext 做快速校验因为它直接验证了 ToUnicode 映射是否存在。我的习惯是把这个验证命令写进 CI 脚本每次修改字体配置或引擎版本后强制跑一遍。曾经有次升级 mPDF 后内置字体表变化导致繁体标点消失就是靠这个自动追加验证字的方法拦住的。希望这套方法,能帮你减少在我电脑上明明正常的困扰。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑