ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

FPDF中文输出终极指南:字体配置、乱码排查与性能优化

2026/9/9 11:22:30 拓冰建站 浏览量
FPDF中文输出终极指南:字体配置、乱码排查与性能优化 简介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基本不可用有限支持支持较完善性能快、轻量偏慢、内存占用大中等偏慢从我这个纯后端开发者的角度如果你只是生成合同、订单、对账单这种结构固定、重复性强的PDFFPDF完全够用学起来也快。如果系统里经常要动态排版、内容来自富文本编辑器、需要输出搜索可复制的PDF那直接上mPDF你会省掉大量调字体、调换行的时间。5.2 我长期使用下来的几点体会踩了不少坑之后我现在处理FPDF相关需求有一套固定流程。拿到任务第一件事永远是检查字体文件的格式和编码链路字体是TTF还是OTF数据源字符串是不是UTF-8代码文件本身用什么编码保存这三个问题确认清楚基本能拦住80%的故障。其次是先做一个最小化Demo只输出一行中文跑通了再往上叠表格、缩进、对齐这些功能。很多人一上来就写完整逻辑结果出问题根本不知道是字体没配对还是参数算错。最后分享一个小习惯把常用的中文字体定义文件统一放在项目的assets/fonts目录里在FPDF初始化时通过构造函数指定字体路径不要让每个脚本各自去找font目录。这样换机器、换环境只要目录结构不变字体就不会出现莫名其妙丢失的情况。FPDF的优势在于轻和简单把字体管理做规范它应付日常的中文PDF输出完全值得信赖。本文还有配套的精品资源点击获取