资讯动态

FPDF输出中文全攻略:从字体注册到宽度计算的避坑指南

发布时间:2026/10/15 14:21:39 来源:尧图企业网站定制
简介面向需要使用PHP生成PDF并支持中文输出的开发者这份资料包以FPDF 1.81核心代码为基础整合了chinese.php中文字体处理脚本、ttf2pt1字体格式转换工具及已转换好的黑体字体定义文件simhei.php可直接在FPDF中调用并输出中文PDF。资源共47个文件包含20个php核心代码与示例、20个字符映射表map、字体转换程序zip包、afm/ttf/z等字体相关格式以及测试用jpg和pdf样例整体压缩包大小约10.4MB结构清晰便于学习和二次开发。已有419人学习浏览适合初识FPDF的PHP开发者或需要完善中文PDF输出功能的技术人员。借助example.php可一键生成中文样例帮助快速理解字体映射与转换流程省去自行配置中文字体的麻烦。1. 标题没说的真相FPDF 默认一个中文字都打不出来先说结论FPDF 是 PHP 生态里最老牌的 PDF 生成库资料一搜一大把但“可输出中文”这件小事能卡住一半以上第一次碰它的人。默认装好 FPDF 后你用Cell()写一句“你好”生成的 PDF 里要么是四个方框要么是一串乱码原因是它的内置字体只覆盖拉丁字符集压根不认识汉字。这篇文章要解决的就是这条链路怎么给 FPDF 接入中文字体、怎么正确写出 UTF-8 中文、怎么处理中文特有的对齐和自动换行以及最常见的翻车点。适合这些读者用 PHP 做合同、发票、报表、标签、证书等技术方案的开发者以及被领导一句“PDF 里怎么是乱码”追着跑的项目维护者。看完之后你能直接照着代码跑通一份中文 PDF并且知道字体、编码、宽度这些参数背后到底在发生什么。2. FPDF 输出中文的原理单字节引擎与字体注册2.1 为什么默认字体遇到中文就“集体失踪”FPDF 内部维护的字体信息是一张单字节映射表每个字符在 PDF 内容流里用一个字节表示。拉丁字符集正好一个字节一个字符但中文在 UTF-8 编码下每个汉字占用三个字节FPDF 在计算字符宽度和写入内容流时会把这仨字节当成三个独立的“拉丁字符”处理于是你看到的要么是方框缺字形要么是形如ä½ å¥½的乱码。这不是 FPDF 有 bug而是它的设计目标本来就是轻量、单字节、面向 Western 文本。官方文档里有一句话被反复引用但很少人细看要使用非拉丁文字必须自己注册包含对应字形的 TrueType 字体。记住关键词字体文件、注册、显式声明三个缺一不可。2.2 中文字体输出的三要素字形文件、AddFont 注册、SetFont 生效要让 FPDF 输出中文链路是固定的。第一步准备一个.ttf字体文件这个文件里必须真实包含汉字字形——拿一个纯英文 TTF 注册一百遍也没用。第二步调用$pdf-AddFont(别名, , 字体文件路径.ttf)把字体加载进当前 PDF 对象。第三步$pdf-SetFont(别名, , 14)让字体生效。这里有个细节容易忽略AddFont的第二个参数是字体样式粗体B、斜体I、粗斜体BI它要求你给每个样式单独准备一个变体文件并分别注册。如果只注册了常规体却在SetFont里写BFPDF 会直接抛异常。常见做法是正文用常规体标题用另一个粗体字体文件注册成不同的别名而不是依赖同一个字体的伪粗体。2.3 字体选型与体积参数为什么字体文件越大越容易出事儿中文字体和拉丁字体最大的区别是体积。一个完整的思源黑体或思源宋体字体文件动辄二三十 MBFPDF 每注册一个字体会把这个字体的宽度表读进内存文档里每页还要引用字体描述信息。体积过大的两个后果一是 PHP 内存消耗肉眼可见地涨二是生成的 PDF 文件被字体数据撑大好几倍。所以给 FPDF 配中文字体时我一般不会直接丢一个全家桶字体进去而是先用字体工具把常用的简体中文字符切一个子集出来。子集覆盖 GB2312 的常用汉字范围大约 6763 个字符就够日常办公文档用了体积能压到 36 MB内存和文件大小都可控。下表是我常用的选型参考用途字体方案子集范围字号建议备注正文宋体风格GB2312 常用字 ASCII1012pt阅读舒适行高需微调标题黑体风格GB2312 常用字 ASCII1418pt粗壮醒目可用独立注册名标签/票据等宽中文字体精简子集810pt对齐要求高宽度表要准确临时验证系统中文字体不切子集任意仅本地测试不建议上生产一个反直觉的点子集切得好不好直接影响 FPDF 能否正常显示字符。如果在SetFont之后写了一个子集里没有的字符FPDF 不会报错而是显示一个空白或方框。所以生产环境里的字体子集必须把业务数据里可能出现的人名、地名、生僻字都覆盖进去这就是为什么我常说“字体子集是中文 PDF 里最容易翻车的隐性参数”。3. 用 FPDF 在本地跑通最小中文示例命令、代码与参数3.1 环境准备与目录结构FPDF 不需要安装扩展只要 PHP 版本不低于 5.6没有特别依赖。不过处理中文时强烈建议安装mbstring扩展后面做宽度计算和字符串截断会频繁用到。目录结构按常见做法拆成三块FPDF 库本体放lib/fpdf/字体文件放fonts/业务脚本放根目录。# 以 Debian/Ubuntu 系为例安装 mbstring sudo apt install php-mbstring # 确认扩展已启用 php -m | grep mbstring需要说明的是FPDF 的Output方法支持五种目标I浏览器输出、D下载、F存文件、S返回字符串、E邮件附件 inline。调试阶段用I最直观生产环境一般用F或S。下面这段是最小的中文输出完整代码你可以直接存成demo.php运行。?php require_once __DIR__ . /lib/fpdf/fpdf.php; // 创建 A4 页面对象单位为毫米mm $pdf new FPDF(); $pdf-AddPage(); // 注册中文字体别名 SongSub, 常规样式, 字体文件路径 $pdf-AddFont(SongSub, , __DIR__ . /fonts/SourceHanSerifCN-Subset.ttf); // 切换当前字体为 SongSub字号 14pt $pdf-SetFont(SongSub, , 14); // 输出一行中文Cell(宽度, 行高, 文本, 边框, 换行, 对齐) $pdf-Cell(0, 10, 你好这是 FPDF 生成的第一份中文 PDF。, 0, 1, L); // 保存到本地文件第二个参数是目标路径 $pdf-Output(F, __DIR__ . /output/chinese_demo.pdf);代码逻辑拆开看Cell的第一个参数0表示宽度占满整行——在毫米单位下A4 的可用宽度是 210 减去左右页边距第二个参数10是这一行的高度单位同样是毫米第三个参数是文本内容0表示不画边框1表示输出完后换行下一行从左边界继续最后一个L是左对齐。AddFont的完整签名是AddFont($family, $style, $file)如果第三个参数省略FPDF 会去font目录里找$family.php这种预编译描述文件给绝对路径的.ttf是最省心的做法FPDF 会自动用内置解析器读取 TTF 的宽度表。执行之后output/chinese_demo.pdf里应该能看到一行正常的中文没有任何方框和乱码。3.2 SetFont 的三个参数别名、样式、字号SetFont($family, $style, $size)决定了文本渲染用的字体。重点说样式参数它接受的合法值是空字符串、B、I、BI对应常规、粗体、斜体和粗斜体。注意FPDF 不会自动为中文生成粗体——拉丁字体可以做字形加粗但 TTF 中文子集一般没有专门的 Bold 变体你写了B它会试着加载名称为别名B的字体文件找不到就抛错。因此我的习惯是中文文档要标题效果就再准备一个黑体风格的字体文件用不同的别名注册比如SongSub管正文、HeiSub管标题然后靠切换SetFont的别名实现视觉层级而不是靠样式参数硬撑。字号参数是 pt 值FPDF 已经做了解析14和14字符串都合法。另一个值得写的参数是行高中文字符全角字形高度占比和拉丁字体明显不同同样 10 毫米行高中文经常显得上下拥挤常见的调法是改成 5.56 毫米这个按你自己的视觉审美微调。3.3 页面尺寸、边距与坐标系FPDF 默认页面是 A4纵向左右边距 10 毫米。注意Cell(0, ...)里宽度的0是从当前左边距一直延伸到右边距的可用宽度这是它最常用的用法。如果你是做面单、小票这类窄幅输出要改用下面这套配置// 宽度 80mm高度 60mm横向边距左右各 5mm $pdf new FPDF(P, mm, array(80, 60)); $pdf-SetMargins(5, 5, 5); $pdf-AddPage();自定义页面尺寸时单位必须和后续用到宽高的地方保持一致FPDF 支持的三种单位mm、pt、in存在换算关系1 英寸等于 72 磅约等于 25.4 毫米。混用单位是新手常踩的坑——用了毫米初始化页面却在SetFont里把字号写成18这个其实没事字号本就是 pt又在Cell里填 20 当宽度结果排版完全对不上。这个坐标系问题在下一章处理中文对齐时还会再次出现。4. 中文文本布局宽度计算、对齐与自动换行的四个参数4.1 GetStringWidth 在中文面前失灵手写一个宽度计算函数FPDF 的GetStringWidth按字节取宽度对一个 UTF-8 编码的汉字它返回的是三个字节对应的宽度之和和实际渲染出来的汉字宽度差别很大导致右对齐时位置肉眼可见地偏。这属于 FPDF 单字节模型的硬伤绕不过去只能在业务层补一个按字符数累加宽度的函数。/** * 计算中英文混排字符串的实际渲染宽度近似用于对齐定位 * param FPDF $pdf 已经 SetFont 的 PDF 实例 * param string $text UTF-8 文本 * return float 宽度值单位与 PDF 对象当前单位一致 */ function smartTextWidth($pdf, $text) { $chars preg_split(//u, $text, -1, PREG_SPLIT_NO_EMPTY); $width 0.0; foreach ($chars as $char) { // 单个字符取宽ASCII 半个字宽中文全角一个字宽 $width $pdf-GetStringWidth($char); } return $width; }逻辑说明preg_split(//u, $text)用空模式配合u修饰符会把 UTF-8 字符串按单字切分避免把汉字的多个字节拆开。GetStringWidth对单个中文字符返回的是当前字体下这个字的宽度对 ASCII 单字符返回半个字宽左右的值累加起来就得到近似真实宽度。这个函数的精度足够支撑右对齐和居中的定位计算比直接拿GetStringWidth($text)算全文精度高得多。4.2 用 MultiCell 做中文自动换行宽度与行高怎么配合实际业务里的中文字段长度不可控Cell的优点是单行精准定位缺点是内容超宽就直接溢出到页边距外不会自动断行。处理长文本的标准做法是用MultiCell它按给定宽度自动换行碰到换行符也会断行。它的核心参数和Cell一致但多了一个行高语义第三个参数是每行的高度FPDF 每换一行就把 y 坐标下移这么多。// 一段可能超过可用宽度的中文简介 $intro 这是一段比较长的中文文本包含标点、数字 123 和英文用来演示 MultiCell 的自动换行行为。; $pdf-SetFont(SongSub, , 12); $pdf-MultiCell(0, 6, $intro, 0, L);参数说明第一个参数0表示宽度沿用整行可用宽度同样支持具体的毫米数值用来做侧边栏这类布局第二个参数6是每行高度我按 12pt 字号的视觉习惯取 6 毫米这个值在中文全文显示时通常比英文紧凑第三个参数0是不画边框L左对齐。注意MultiCell的换行依据是宽度不是中文字符的“自然断词点”中文每个字都可以换行表现反而比英文更平滑英文长单词超宽时不会断开整行会溢出这个是 FPDF 的另一个已知局限只能在上游先做字符截断。4.3 表格场景下把 Cell 和 MultiCell 混用的对齐问题做发票、名册这类带边框的页面时一个常见的需求是“单元格内文本自动换行但表格边框不破”。实际写的时候我会用固定宽度的MultiCell负责文本用Rect画外框而不是指望Cell一次搞定。$colX 10; // 列起始 x 坐标 $colW 40; // 列宽 40mm $lineH 6; // 行高 6mm $y $pdf-GetY(); // 画单元格边框Rect(x, y, 宽, 高) $pdf-Rect($colX, $y, $colW, $lineH); // 文本输出宽度固定高度用行高自动换行 $pdf-SetXY($colX 2, $y 1); // 左边距 2mm上下各留 1mm 内白 $pdf-MultiCell($colW - 4, 5, $cellText, 0, L);这里有个状态机的细节SetXY之后MultiCell会改变GetY()的位置画下一行外框前必须重新读取GetY()而不是用原始$y累加否则行高累计误差会越来越大。内白靠$colX 2和$colW - 4手工实现这是 FPDF 没有原生 padding 参数下的标准做法。另外MultiCell的换行行高我给的是5而不是边框行的6目的是让换行文本之间更紧凑避免看着像行间距过大——这种数值差异就是排中文版式里真正吃时间的部分。5. FPDF 中文输出避坑指南五个高频故障与排查思路5.1 输出 PDF 全是方框一个汉字都看不见现象PDF 能生成页面内容完整但所有汉字显示成空心的方框。原因字体文件被注册了但字体文件本身不包含汉字字形或者AddFont用的字体文件路径错误导致 FPDF 悄悄加载了内置的拉丁字体。最常见的情况是拿一个只含 128 个字符的迷你字体当中文用。解决确认.ttf文件的真实字符覆盖用字体查看器或fc-query这类命令检查它是否包含 CJK 统一表意文字。代码里做一件事AddFont后立即SetFont并调用$pdf-Text(10, 10, 测)然后用 PDF 查看器检查这个“测”是否正常显示如果这一步都不行问题就在字体文件本身。5.2 中文正常但标点符号变成了半角甚至消失现象正文汉字全部正常但逗号、引号、顿号看着很怪有的偏窄有的像被截了一刀。原因中文字体子集在切分时经常只保留了汉字和 ASCII把全角标点U3000U303F 区间漏掉了FPDF 找不到字形就回退到一个空字符标点就“消失”或错位。解决切子集时不要只按“常用汉字列表”切把 GB2312 全角标点区和常用符号区一起保留。我在生产里是把汉字区、全角标点区、ASCII 可见字符三个区间打包切进一个子集这样中文排版才完整。5.3 文本正确但右对齐时整体偏右好几毫米现象用Cell($w, $h, $text, 0, 0, R)做右对齐中文行看起来明显没有贴到右边线位置时准时不准。原因就是第 4 章讲的GetStringWidth按字节计算导致的。FPDF 内部做R对齐时用的是当前字体的字节宽度表对 UTF-8 中文计算出来的宽度大于真实渲染宽度于是右对齐为了“塞开”内容把位置推过了。解决不依赖Cell的对齐参数做中文右对齐改用Cell的左对齐 手工计算 x 坐标。先算出文本真实宽度再用“右边距 - 文本宽度”作为 x 起点输出。$rightEdge $pdf-GetPageWidth() - $pdf-GetX(); // 这里以左边距为参考做简化精确值直接取 lMargin w - txtW $txtW smartTextWidth($pdf, $title); $pdf-SetX($pdf-lMargin $pdf-w - $pdf-rMargin - $txtW); $pdf-Cell($txtW, 8, $title, 0, 1, L);5.4 AddFont 报错无法打开字体文件或不是有效的 TrueType现象AddFont直接抛异常提示字体文件不存在或者“not a valid TrueType font”而且报错信息不给出具体文件。原因路径问题占了七成FPDF 的AddFont接收相对路径时是相对于 FPDF 类所在目录解析的业务脚本里写的fonts/xxx.ttf经常指向错误的位置另外某些系统自带的.ttc合集文件FPDF 1.8.x 解析不了。解决一律用绝对路径传参并在调用前加一个is_file()断言。.ttc文件优先转成单.ttf再用这是最省时间的办法。5.5 打开 PDF 提示字体未嵌入打印或转 PDF/A 变样现象用浏览器打开正常但交给印刷系统或做长期归档校验时报告“字体未嵌入”部分环境渲染成替换字体版式完全错乱。原因FPDF 对 TTF 的处理是读取字形宽度并引用字体文件但生成的 PDF 内嵌字体信息的方式取决于 TTF 是否包含有效的嵌入许可标志。一些精简后的子集字体subsetting工具会把OS/2表中的嵌入权限位改掉FPDF 会读这个位来决定是否内嵌。解决切子集时选择“允许嵌入”的选项生成后用 PDF 检查工具确认字体是否内嵌。如果内嵌失败换一个未修改过嵌入标志的字体直接注册别在这种玄学问题上跟 FPDF 硬刚太久。下表把上面几条串成一个速查现象常见根因首选排查动作全篇方框字体无中文字形检查 TTF 字符覆盖标点异常子集漏了全角标点区重新切子集包含标点区右对齐偏移GetStringWidth 按字节算宽用 smartTextWidth 手工定坐标AddFont 报错路径或 .ttc 格式不兼容转绝对路径 转 .ttf字体未嵌入嵌入许可位被改检查工具链嵌入选项6. 进阶字体复用、批量生成与输出校验的习惯如果只有一两份 PDF前面的代码已经够了。但真实项目里往往要连续生成几百份合同或单据这时候有两个值得养成的习惯。第一个习惯是复用 PDF 对象与字体注册。AddFont是一次性操作同一个字体文件在同一个 PHP 进程里注册一次就行多份文档用同一个页面模板时我会建一个工厂对象把AddPage()、AddFont、SetFont、固定的SetMargins都封装进去循环里只调用数据填充方法避免每份都重新读一遍几十 MB 的字体宽度表。内存和耗时的节省是实打实的。function makeInvoicePdf($orderNo, $amount, $customerName) { $pdf new InvoiceTemplate(P, mm, A4); // 封装好的模板类 $pdf-AddPage(); $pdf-SetFont(SongSub, , 12); $pdf-Cell(0, 6, 订单号 . $orderNo, 0, 1); $pdf-Cell(0, 6, 客户 . $customerName, 0, 1); // ... 更多数据行 return $pdf-Output(S); // 返回字符串由调用方统一写文件 }第二个习惯是批量生成后抽检——找个命令行工具批量提取 PDF 文本和页数确认页数没少、中文没乱码再交给下游。某次我用 FPDF 做了四十多份报价单文件名和内容都检查过了唯独页面总数比预期少了两页后来发现是数据里有一条超长文本把版式撑破了MultiCell溢出到页面外直接不渲染。那之后我习惯生成完先跑一轮文本提取与页数统计付出几分钟省掉的是交付后整段返工的血泪教训。希望这篇笔记能帮你把 FPDF 中文这条路一次走通。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑