PHP 图片脚本里,imagettftext往图上写中文时突然冒出Warning: any2eucjp(): invalid code in input string,验证码、海报、pChart 图表都可能被这一行打断。我这次没有一上来就改 php.ini,而是把排查交给了 Codex:先到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建一把 Key,再把 Base URL 填成https://taotoken.net/api,让 Codex 按老步骤检查 simhei.ttf 路径、确认 GD 的 JIS-mapped Japanese Font Support 是否 enabled。整个过程里,Codex 的模型请求走 TaoToken 下发,PHP 侧只做本地执行,最终用mb_convert_encoding转码把中文正常画回图片上,报错也不再打断生成流程。排查的关键不是猜编码,而是按顺序确认字体文件与 GD 编译选项,这两点确认完,修复方案基本就出来了。
1. 先复现 imagettftext 的 any2eucjp 报错,别急着改 php.ini
1.1 中文文字没画上去,先多了一行 Warning
下面这段脚本是图片上写中文最常见的写法:
<?php $pic = imagecreatetruecolor(250, 30); $black = imagecolorallocate($pic, 0, 0, 0); $white = imagecolorallocate($pic, 255, 255, 255); $font = 'C:/Windows/Fonts/simhei.ttf'; $str = '中华人民共和国'; imagettftext($pic, 10, 0, 10, 20, $white, $font, $str); header('Content-type: image/png'); imagepng($pic, 'output.png'); imagedestroy($pic);在大多数 PHP 环境里,这段代码能把汉字画出来。但如果你拿到的是别人编译的 PHP 包,或者当初编译时为了支持日文字体多开了一个参数,浏览器访问时不一定看得到报错,反而在命令行执行时先出现一行:
Warning: imagettftext(): any2eucjp(): invalid code in input string in C:\xampp\htdocs\make_pic.php on line 9注意这个 Warning 是从imagettftext内部冒出来的,不是mb_convert_encoding这类函数报的。它说明字符串已经进了 GD 的字体渲染流程,GD 在内部尝试做一次 EUC-JP 转换,但输入字符串里出现了它认为非法的字节。于是图片上「中华人民共和国」没有正常画出来,生成流程直接被打断。
1.2 根因是编译 PHP 时的 --enable-gd-jis-conv,不是你的业务代码
编译 PHP 时如果加了--enable-gd-jis-conv,GD 就带上了JIS-mapped Japanese Font Support。这个选项本意是让 FreeType 正确渲染日文字体,但实际实现里,imagettftext在处理非 ASCII 字符时,会先把 UTF-8 字符串按日文规则转一遍。中文虽然也是方块字,但它不是日文,编码字节不符合 GD 的预期,转换时就触发any2eucjp(): invalid code in input string。
这个问题在 PHP 官方 bug 系统里有记录,地址是 bugs.php.net/bug.php?id=42218,挂了很多年。它和你的业务代码无关,也不是把mb_internal_encoding改成 UTF-8 就能解决的。最麻烦的是,你无法通过 php.ini 直接关闭这个 JIS 开关,因为它是编译期定下来的,运行期没有对应配置项。所以排查方向要放在「确认开关状态」和「绕开转换」这两件事上。
2. 给 Codex 配好 TaoToken:Key 从官网创建,Base URL 填 https://taotoken.net/api
2.1 去官网创建一把 YOUR_API_KEY
在动手查 simhei.ttf 之前,先把 Codex 能用的 API 通道准备好。打开 TaoToken,注册登录后,在控制台创建一把 API Key,拿到的字符串就是后面要填的YOUR_API_KEY。这个平台是统一接入通道,官网主要做三件事:创建 Key、看模型广场、看用量。模型 ID 不靠猜,以 TaoToken 模型广场 当时列表为准。
这里有两类地址要分清:给人点的官网落地页是https://taotoken.net/?utm_source=taotoken_aicg_blog_end;给 Codex 填的接口 Base URL 是https://taotoken.net/api,末尾不用加/v1。前者在浏览器里打开,后者出现在配置文件里。
2.2 在 ~/.codex/config.toml 里注册 TaoToken 模型提供方
Codex 使用~/.codex/config.toml来声明模型提供方。如果这个文件不存在,手动创建。配置如下:
model = "YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY"保存后,在终端里把 Key 写进环境变量:
export TAOTOKEN_API_KEY=YOUR_API_KEY然后启动 Codex,先不要急着让它写代码,发一句「请确认你已经就绪,并列出你将要执行的排查步骤」。如果它正常返回了,说明 TaoToken 通道已经通;如果返回 401,回控制台检查 Key 是不是复制多了空格。
2.3 为什么不直接改 php.ini
如果目标只是让这张图片不报错,确实可以直接去服务器上找 PHP 安装包重新编译,或者换一台没开 JIS 的机器。但很多项目不止一个环境:开发机、测试机、客户服务器各一套,有些是 Docker 镜像,重编译成本很高。让 Codex 先把问题定位清楚,再决定用转码还是重编译,才是改动最小的一条路。
3. 按原文排查顺序让 Codex 走一遍:字体路径 → GD JIS 开关
原文的排查顺序很明确:先确认 simhei.ttf 路径,再检查 GD 的 JIS 开关,最后才决定转码还是重编译。按这个顺序走,不会把问题误判成字体缺失。
3.1 第一步:确认 simhei.ttf 存在且路径可读
在本地终端执行:
fc-list | grep -i simhei如果输出里包含/usr/share/fonts/truetype/simhei.ttf这类路径,说明字体存在。如果没有任何输出,说明服务器上根本没有 simhei.ttf,需要换用其它中文字体,比如 simsun.ttc 或 wqy-zenhei.ttc,并同步修改 PHP 里的$font变量。
把这条命令的输出贴回 Codex 对话,它会判断当前路径能否被 FreeType 正常加载。如果你写的是 Windows 路径C:/Windows/Fonts/simhei.ttf,而实际上代码跑在 Linux 容器里,Codex 会提示你路径写错了。注意这里不需要让 Codex 登录服务器执行任何命令,只需要你在本地把结果贴回去,由它来分析。
3.2 第二步:检查 GD 的 JIS-mapped Japanese Font Support
用一条命令查看 JIS 开关:
php -i | grep -i jis更直观的方式是打印 GD 信息:
<?php $info = gd_info(); echo 'JIS-mapped Japanese Font Support: ' . ($info['JIS-mapped Japanese Font Support'] ?? 'not available') . PHP_EOL;把执行结果贴给 Codex。如果显示enabled,就坐实了编译参数里带着--enable-gd-jis-conv;如果显示disabled或not available,那 any2eucjp 报错就不一定是这个原因,需要先用mb_check_encoding($str, 'UTF-8')检查输入字符串本身是否非法。
4. 落地修复:mb_convert_encoding 转码优先,重编译兜底
排查完成后,Codex 通常会给出两条修复路径。先用转码方案,成本最低;重编译 PHP 是根治,但要动编译参数,风险也大。
4.1 把 UTF-8 中文转成 HTML 实体再给 imagettftext
核心修复是把汉字转成 HTML 实体形式:
$str = mb_convert_encoding($str, 'html-entities', 'UTF-8');这行代码会把「中华人民共和国」变成类似哪一人民共和国的数字实体。GD 渲染时看到的是纯 ASCII 数字,不再把它当日文处理,画到图片上仍然是中文。
完整的可运行版本:
<?php // make_pic.php $pic = imagecreatetruecolor(250, 30); $black = imagecolorallocate($pic, 0, 0, 0); $white = imagecolorallocate($pic, 255, 255, 255); $font = '/usr/share/fonts/truetype/simhei.ttf'; $str = '中华人民共和国'; $str = mb_convert_encoding($str, 'html-entities', 'UTF-8'); imagettftext($pic, 10, 0, 10, 20, $white, $font, $str); header('Content-type: image/png'); imagepng($pic, 'output.png'); imagedestroy($pic);注意mb_convert_encoding的输入必须是合法 UTF-8。如果字符串本来是从 GBK 文件里读进来的,先转成 UTF-8 再做 HTML 实体转换:
$str = mb_convert_encoding($str, 'UTF-8', 'GBK'); $str = mb_convert_encoding($str, 'html-entities', 'UTF-8');4.2 去掉 --enable-gd-jis-conv 编译选项
如果项目里几十处 imagettftext 都不方便改,再考虑重编译 PHP。先把当前编译参数保存下来:
php -i | grep configure在输出的 configure 命令里找到--enable-gd-jis-conv并去掉,然后重新编译:
make clean make make install编译完成后重启 PHP-FPM:
systemctl restart php-fpm再次运行php -i | grep -i jis,确认JIS-mapped Japanese Font Support不再是 enabled。如果这台机器的 PHP 是 apt 或 yum 装的,没有源码目录,就别走这条路径,优先用转码方案。
5. pChart 图表里的中文标签也按同一套转码处理
原文后半部分用 pChart 画饼图时,图例里的中文同样会触发这个报错。pChart 底层画文字时一样调用 imagettftext,所以只要 JIS 开关是 enabled,中文标签就躲不过。
5.1 AddPoint 之前先递归转码数组
pChart 的标签通常是一个数组:
$months = array('Jan', '二月', '三月', 'Apr', 'May');不能直接把数组塞给 AddPoint,要先逐个转成 HTML 实体。写一个递归函数可以用在多个图表上:
function utf8ToHtmlEntity($value) { if (is_array($value)) { return array_map('utf8ToHtmlEntity', $value); } return mb_convert_encoding($value, 'html-entities', 'UTF-8'); } $months = utf8ToHtmlEntity(array('Jan', '二月', '三月', 'Apr', 'May')); $DataSet->AddPoint($months, 'Serie2');递归版本的好处是数组里不一定全是字符串,也可能嵌套分组,一层 foreach 不一定覆盖全,递归能保证每个元素都处理到。
5.2 给项目准备一个公共的文本转换函数
如果项目里有多处 pChart 或 imagettftext 调用,可以把转码函数放进公共文件,所有入图的文字统一走它:
function textForGd($str) { return mb_convert_encoding((string) $str, 'html-entities', 'UTF-8'); }然后调用处统一使用:
imagettftext($pic, 10, 0, 10, 20, $white, $font, textForGd($str));这样即使以后把代码迁到一台开了 JIS 的机器上,也不会因为漏掉某个调用而再次报错。
6. 验证图片、清掉报错,再回控制台对一下调用消耗
6.1 本地跑一遍 PHP 脚本,输出图片和 CLI 日志
修复后至少要验证两个点:中文渲染正确,Warning 消失。在终端执行:
php make_pic.php脚本里已经用imagepng($pic, 'output.png')保存了图片,打开 output.png 看到「中华人民共和国」六个字正常出现,同时命令行里没有任何 any2eucjp 输出,说明转码生效了。如果还有报错,大概率是还有某个 imagettftext 调用没用textForGd,按报错行号找过去改掉就行。
6.2 回 TaoToken 控制台看这次 Codex 对话的用量
Codex 排查过程中,你对它发的每条消息、它生成的每段排查步骤,都是经由刚才配置的 Base URL 走 TaoToken 发到模型的。回到 TaoToken 控制台,打开用量页面,能看到这次对话的 token 消耗和请求记录。这能同时验证两件事:Key 有效,且请求确实走到了 TaoToken,而不是还在用本机默认配置。
6.3 转码后常见的三个坑
- 报错变成
Could not find/open font:字体路径不对,回 3.1 节用 fc-list 确认。 - 转码后文字变方块:字体文件本身不支持中文,比如用 arial.ttf 画中文就会出现方块,换成 simhei.ttf 或 wqy-zenhei.ttc。
- 图片能显示,但 pChart 图例里还是英文:
utf8ToHtmlEntity只处理了你传入的数组,如果 pChart 内部拼接了中文常量,需要把这些常量也统一走textForGd。
验证通过之后,如果接下来要用同一把 Key 做更多模型测试,可以在 TaoToken 模型对话 里先发一条消息,确认模型切换、上下文长度都没问题;日常写代码排查如果量比较大,可以打开 Coding Plan 看套餐是否够用;需要新建一把专门给 Codex 用的 Key,直接去 控制台 API Keys 创建。