FPDF中文输出终极指南:字体配置、乱码排查与性能优化
2026/9/9 11:22:26 网站建设 项目流程

简介:FPDF是一套常用的PHP PDF生成库,默认字体对中文支持不佳,这份资料正是围绕“用FPDF输出中文”整理的配套工具与示例。面向需要快速生成带中文PDF的PHP开发者,省去自行转换字体、配置编码的麻烦。压缩包共47个文件,约10.4MB,包含FPDF核心代码、chinese.php中文字体支持文件、ttf2pt1字体格式转换工具,以及已转换好的黑体字体simhei.php等,另有map字体映射、字体定义文件与示例PDF。直接运行example.php即可看到中文输出效果,便于验证和二次开发。目前已有419人学习,适合刚接触FPDF、希望快速实现中文导出的初中级开发者,也可作为日常开发中的参考工具包。 做后端开发的朋友应该都有这种体验——功能都写完了,最后被一张带中文的PDF卡住。FPDF这个轻量级PDF生成库我用了好几年,最大的感触就是:英文一行搞定,中文一调就崩。网上搜FPDF中文输出,资料确实不少,但多数要么版本太老,要么只给结论不给原理,照着抄都容易翻车。这篇文章把我踩过的坑和最终沉淀下来的方案整理出来,从字体准备到代码实现,从乱码排查到性能优化,一次讲透。无论你是刚开始接触FPDF,还是已经上手但被中文折腾过,这篇都值得花几分钟看完。

1. 为什么FPDF输出中文这么麻烦:核心原理与问题根源

1.1 PDF字体机制与FPDF的“英文基因”

先弄清楚一个基础事实:PDF文档本身并不直接存储“汉字”这回事,它存储的是字符编码和字体映射关系。FPDF内置的核心字体,比如Helvetica、Times、Courier,严格来说是PostScript标准字体集,这些字体只覆盖拉丁字符和扩展字符集(Latin-1之类)。也就是说,FPDF原生状态下压根不认识中文,你给它一串中文,它只能当成一堆未知编码的字节流去处理,最终输出来的就是乱码或者干脆显示空白。

FPDF后来版本引入了AddFont方法,允许你嵌入外部TrueType字体文件。这一步相当于给FPDF装上了中文字库,但仅仅“装上”还不够。PDF引擎在处理文字时,需要知道每个字符对应的字形、每个字形占多宽、字体文件是否嵌入、字符映射表是否完整。FPDF本身是为西方语言设计的,在很多内部逻辑里都假设一个字符等于一个字节,这个假设在中文面前就完全失效了。

1.2 中文字体成功输出的关键:$unifont与UTF-16转换

那么FPDF最后是怎么做到输出中文的呢?关键在makefont工具生成的字体定义文件(.php)里,有一个标志位叫$unifont。当你用官方makefont.php把中文字体转成定义文件后,这个标志会被置为true。FPDF检测到这个标志后,会自动切换内部的文本处理模式:先把传入的UTF-8字符串转成UTF-16BE编码,再逐字读取每个Unicode码点,通过字符宽度表($charWidths)拿到准确的字宽。这样排版引擎才能为每个汉字分配正确的宽度,输出到PDF后阅读器也能通过字形索引找到对应的汉字。

这个过程听起来不复杂,但实际工程里只要有一个环节没对齐,比如字体文件本身是OTF格式而makefont只支持TTF轮廓、或者输入字符串不是UTF-8编码,就会全盘出错。理解了这一层,你再看后面的步骤就会很通透,所有操作其实都是在解决“如何让FPDF拿到正确的中文字宽和字形映射”这一个问题。

2. 中文字体准备与字体定义生成

2.1 字体选择:开源优先,格式必须为TrueType

做FPDF中文输出,第一步不是写代码,而是选字体。很多教程直接让你用系统里的微软雅黑、宋体之类,我的建议是:商业项目里尽量避免,版权风险很高。用思源黑体(Noto Sans CJK / Source Han Sans)或者文泉驿正黑这类开源字体,协议宽松,印刷和屏幕显示效果都过关。我个人的习惯是:正文用Noto Serif SC(思源宋体),标题用Noto Sans SC,搭配起来层次感很好。

字体格式这个坑必须单独说明一下。FPDF的makefont工具依赖TrueType字体轮廓结构,它解析的是glyf表。思源黑体的官方发行版通常提供OTF格式,OTF里的CFF轮廓结构makefont不支持,直接转会报错或者生成的宽度表是空的。所以下载字体时请认准TTF格式。如果只有OTF,可以用FontForge或在线转换工具把它转存为TrueType格式,转换完以后最好再用fonttools之类的工具检查一遍,确认字体内部没有损坏。

2.2 用makefont.php生成字体定义文件

字体文件准备完毕后,找到FPDF目录下的makefont/makefont.php脚本,在命令行里执行:

php makefont.php NotoSansSC-Regular.ttf

如果字体文件和脚本不在同一目录,需要写全路径。执行完成后,会在同目录下生成一个NotoSansSC-Regular.php文件,这个就是字体定义文件。里面保存了字体名称、字符宽度数组、Unicode标志位等关键信息。把这个php文件以及原始的ttf文件一起复制到FPDF的font目录下,字体配置就算完成了。

粗体字体也需要提前生成好。FPDF的AddFont方法支持样式参数,比如普通样式和粗体样式会分别注册。但要注意,粗体和常规字体如果属于同一字重家族,必须在AddFont时使用同一个字体家族名,只是样式后缀不同。用思源黑体的时候,我会把NotoSansSC-Medium作为粗体替代方案,因为思源黑体通常在500或700处才有明显粗细变化,直接映射到PDF的B样式上显示效果更协调。

3. 核心代码实例:三步跑通中文PDF输出

3.1 最简实现:在指定坐标输出中文

做完上面的准备工作,代码部分其实很短。一个最基础的FPDF中文输出Demo长这样:

<?php require('fpdf/fpdf.php'); $pdf = new FPDF(); // 注册中文字体:第一个参数是字体家族名,第二个是样式,第三个是定义文件名 $pdf->AddFont('NotoSansSC', '', 'NotoSansSC-Regular.php'); $pdf->AddPage(); $pdf->SetFont('NotoSansSC', '', 12); $pdf->Cell(0, 10, '你好,FPDF 中文输出', 0, 1, 'L'); $pdf->Output('I', 'demo.pdf');

这里有几个容易出错的细节。AddFont的第一个参数只是逻辑名称,你可以随便起,只要SetFont的时候保持一致即可。第二个参数是样式标识:空字符串代表常规,B代表粗体。第三个参数是前面makefont生成的php文件名,如果字体文件不在默认font目录,也可以写相对路径。

跑一下这段代码,浏览器会直接输出一个带中文的PDF。如果一切正常,你会看到“你好,FPDF 中文输出”这几个字清晰显示。如果报错,大概率是字体定义文件没找到,或者ttf文件路径不对。

3.2 复杂场景:中文表格、自动换行与对齐

实际项目中不会只输出一行文字,中文表格才是最常见的需求。FPDF的Cell和MultiCell都能正常处理中文,前提还是字体已经注册。MultiCell更适合长文本自动换行:

<?php $pdf = new FPDF(); $pdf->AddFont('NotoSansSC', '', 'NotoSansSC-Regular.php'); $pdf->AddPage(); $pdf->SetFont('NotoSansSC', '', 10); // 标题 $pdf->Cell(0, 8, '项目进度汇报', 0, 1, 'C'); // 长文本自动换行 $content = '这是一段比较长的中文内容,用来测试FPDF在MultiCell场景下是否能正确按照中文字符宽度自动换行。实际项目中,这块数据往往来自数据库,可能包含用户输入的备注、评论等内容。'; $pdf->MultiCell(0, 7, $content, 0, 'L');

MultiCell第一个参数传0表示使用页面可用宽度,第二个参数是行高,第三个参数是文本内容。做过电商项目的都知道,商品名称、收货地址这类字段经常包含中文和数字混排,MultiCell自动换行能处理得不错。

再往前一步,生成一个简单的中文表格,可以用循环加Cell实现:

<?php $pdf = new FPDF(); $pdf->AddFont('NotoSansSC', '', 'NotoSansSC-Regular.php'); $pdf->AddPage(); $pdf->SetFont('NotoSansSC', '', 10); // 表头 $header = ['商品名称', '数量', '单价', '金额']; $widths = [70, 20, 30, 30]; for ($i = 0; $i < count($header); $i++) { $pdf->Cell($widths[$i], 8, $header[$i], 1, 0, 'C'); } $pdf->Ln(); // 数据行 $data = [ ['USB数据线 1米', 2, '15.00', '30.00'], ['无线蓝牙耳机', 1, '99.00', '99.00'], ]; foreach ($data as $row) { $pdf->Cell($widths[0], 8, $row[0], 1, 0, 'L'); $pdf->Cell($widths[1], 8, $row[1], 1, 0, 'C'); $pdf->Cell($widths[2], 8, $row[2], 1, 0, 'C'); $pdf->Cell($widths[3], 8, $row[3], 1, 0, 'C'); $pdf->Ln(); } $pdf->Output('I', 'table.pdf');

中文单元格的宽度设置需要稍微留出余量。一个14号字的中文字大约占7毫米宽度,如果是10号字大约3.5毫米,根据内容长度估算列宽时,记得按这个规律计算,不要照抄英文场景下的宽度参数。

4. 常见问题排查与避坑实录

4.1 高频报错与对策速查表

FPDF用久了,你会发现很多问题不是代码逻辑错了,而是工程环境、字体、编码之间的摩擦。我整理了一个速查表,这里每一项都是实际踩过的:

现象常见原因解决办法
中文全部变成乱码使用FPDF内置字体,没有AddFont中文字体注册中文字体后再SetFont
中文显示为方框或空白字体定义文件损坏,或字体不是TTF格式用makefont重新生成定义文件
内容重叠、换行错乱字体Unicode标志没有被正确识别检查php定义文件是否存在$unifont = true;
输出文件下载时文件名乱码浏览器对中文文件名处理方式不同使用纯英文文件名,或参考下面的文件名处理方案
代码文件本身是GBK编码,中文直接报错PHP字符串编码与FPDF要求不一致统一使用UTF-8编码保存代码文件

这里特别想说一下文件名的问题。$pdf->Output('D', '中文报表.pdf')在本地测试可能正常,但部署到线上Linux环境下,浏览器下载时文件名经常会变乱码。我的做法是统一让PDF文件名用英文或者日期时间戳,PDF内部标题文字再用中文展示,既避免了文件名乱码,也方便自动化系统做归档。

4.2 细节经验:中文复制乱码、粗体字体、文件大小优化

还有一个深入一点的坑:用FPDF生成的PDF,中文内容在阅读器里看没问题,但当你选中文字复制出来,得到的往往是乱码或者一堆类似“□□□”的符号。原因很简单,FPDF没有为嵌入字体生成ToUnicode映射表。PDF规范里,ToUnicode CMap是文本复制、检索、无障碍阅读的关键结构。好在大多数使用场景只是展示和打印,复制乱码的影响不大;如果业务方明确要求文字可复制、可检索,建议直接换mPDF或TCPDF,不要在原版FPDF上强行加这个功能,性价比太低。

粗体字体的处理也值得记录一下。我接手过一个合同系统,标题要加粗,但当时只注册了常规字体,SetFont('NotoSansSC', 'B', 14)之后系统提示找不到字体定义。正确的做法是提前用粗体字体文件生成另一个字体定义:

php makefont.php NotoSansSC-Bold.ttf

然后在代码里注册:

$pdf->AddFont('NotoSansSC', 'B', 'NotoSansSC-Bold.php');

这样SetFont的时候指定B样式才能真正生效,否则会退回常规字重,甚至直接报错。

文件大小的问题通常出现在中文字体引入之后。FPDF默认会把整个TTF文件嵌入PDF,而中文字体动辄几兆,导致生成的PDF从几十KB飙升到几MB。如果只是做内部系统报表还无所谓,但需要通过网络传输给外部用户时,体验就差了。想压缩体积,优先检查是不是同一个字体家族同时嵌入了多个字重文件,只保留实际用到的字体可以省下一部分体积。如果想要更激进的大小优化,建议换成支持字体子集化的库。

5. 方案选型总结与个人建议

5.1 FPDF、TCPDF、mPDF怎么选

这个部分写给还在犹豫方案的读者。FPDF的基础能力是输出简单PDF文档,配合自定义字体后可以处理中文,但遇到复杂排版、HTML转PDF、文本复制检索这些进阶需求时,短板就暴露了。下面这张表是我按照实际项目经验做的对比:

维度FPDFTCPDFmPDF
原生中文支持需要手动添加字体内置多种亚洲字体,开箱可用内部基于FPDF,中文体验完善
UTF-8支持有限,依赖字体定义原生支持原生支持
文本复制不乱码不支持支持ToUnicode映射支持ToUnicode映射
字体子集化不支持支持支持
从HTML转PDF基本不可用有限支持支持较完善
性能快、轻量偏慢、内存占用大中等偏慢

从我这个纯后端开发者的角度,如果你只是生成合同、订单、对账单这种结构固定、重复性强的PDF,FPDF完全够用,学起来也快。如果系统里经常要动态排版、内容来自富文本编辑器、需要输出搜索可复制的PDF,那直接上mPDF,你会省掉大量调字体、调换行的时间。

5.2 我长期使用下来的几点体会

踩了不少坑之后,我现在处理FPDF相关需求有一套固定流程。拿到任务第一件事永远是检查字体文件的格式和编码链路:字体是TTF还是OTF?数据源字符串是不是UTF-8?代码文件本身用什么编码保存?这三个问题确认清楚,基本能拦住80%的故障。其次是先做一个最小化Demo,只输出一行中文,跑通了再往上叠表格、缩进、对齐这些功能。很多人一上来就写完整逻辑,结果出问题根本不知道是字体没配对,还是参数算错。

最后分享一个小习惯:把常用的中文字体定义文件统一放在项目的assets/fonts目录里,在FPDF初始化时通过构造函数指定字体路径,不要让每个脚本各自去找font目录。这样换机器、换环境,只要目录结构不变,字体就不会出现莫名其妙丢失的情况。FPDF的优势在于轻和简单,把字体管理做规范,它应付日常的中文PDF输出完全值得信赖。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询