PHP函数imagettftext()和Unicode

12

我正在使用PHP函数imagettftext()将文本转换为GIF图像。我要转换的文本包含日语等Unicode字符。在我的本地计算机(Ubuntu 7.10)上一切正常,但在我的网络主机服务器上,日语字符会变得混乱。这可能是什么原因导致的呢?一切都应该编码为UTF-8。

网络主机服务器上的损坏图片: http://www.ibeni.net/flashcards/imagetest.php

来自我的本地计算机的正确图片副本: http://www.ibeni.net/flashcards/imagetest.php.gif

来自我的本地计算机的phpinfo()副本: http://www.ibeni.net/flashcards/phpinfo.php.html

来自我的网络主机服务器的phpinfo()副本: http://example5.nfshost.com/phpinfo

代码:

mb_language('uni');
mb_internal_encoding('UTF-8');

header('Content-type: image/gif');

$text = '日本語';
$font = './Cyberbit.ttf';

// Create the image
$im = imagecreatetruecolor(160, 160);
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);

// Create some colors
imagefilledrectangle($im, 0, 0, 159, 159, $white);

// Add the text
imagettftext($im, 12, 0, 20, 20, $black, $font, $text);
imagegif($im);
imagedestroy($im); 
5个回答

13

我一直遇到一个问题,就是编写一个脚本以在图像中渲染文本并输出。问题是,由于不同的浏览器(或者说代码的难度/偏执程度,你想怎么想就怎么想),我无法知道在$_GET数组中放置了什么编码。

这是我解决问题的方法。

$item_text = $_GET['text'];

// Detect if the string was passed in as unicode
$text_encoding = mb_detect_encoding($item_text, 'UTF-8, ISO-8859-1');
// Make sure it's in unicode
if ($text_encoding !== 'UTF-8') {
    $item_text = mb_convert_encoding($item_text, 'UTF-8', $text_encoding);
}

// HTML numerically-escape everything (&#[dec];)
$item_text = mb_encode_numericentity($item_text,
    [0x0, 0xffff, 0, 0xffff], 'UTF-8');

这解决了任何使用imagettftext时无法处理127以上字符的问题,方法是将所有字符(包括多字节的Unicode字符)转换为它们的HTML数值字符实体——例如将"A"转换为"A","B"转换为"B"等等。手册页面声称支持这种方式。


对我也起作用了。我试图打印 TM 字符。虽然我尝试的所有字体都有这个字符,但只能在某些字体中工作。 - Jarett Millard
mb_convert_encoding在PHP 8.2中已被弃用。https://php.watch/versions/8.2/utf8_encode-utf8_decode-deprecated。可能的建议修复方法-https://github.com/Kristories/symfony/commit/eea3b10327c1a9b1298a069ea8f844c70e73b6a7 - Dennis

13

以下是最终对我有效的解决方案:

$text = "你好";
// Convert UTF-8 string to HTML entities
$text = mb_convert_encoding($text, 'HTML-ENTITIES',"UTF-8");
// Convert HTML entities into ISO-8859-1
$text = html_entity_decode($text,ENT_NOQUOTES, "ISO-8859-1");
// Convert characters > 127 into their hexidecimal equivalents
$out = "";
for($i = 0; $i < strlen($text); $i++) {
    $letter = $text[$i];
    $num = ord($letter);
    if($num>127) {
      $out .= "&#$num;";
    } else {
      $out .=  $letter;
    }
}

将字符串转换为HTML实体是可行的,但是函数imagettftext()不接受命名实体。例如:
&#26085;&#26412;&#35486;

是可以的,但是

&ccedil;

但把它转换回 ISO-8859-1,将命名实体重新转换为字符时,出现了第二个问题。imagettftext() 不支持值大于 127 的字符。最终的 for 循环会将这些字符编码成十六进制。对于我使用的文本(包括日语、中文以及葡萄牙语带重音符号的拉丁字符),该解决方案有效,但我不能百分之百确定它在所有情况下都有效。

所有这些花招都是因为 imagettftext() 在我的服务器上并不真正接受 UTF-8 字符串。


2
为什么要进行UTF-8 > HTML实体 > ISO-8859的转换,而不是直接进行UTF-8 > ISO-8859的转换? - deceze
+1 正如 deceze 所提到的,我可能会选择 iconv('UTF-8', 'ISO-8859-1', $text) 而不是实体方法,但除此之外,转换为 十六进制表示 是正确的方式!感谢您的提示! - Levite
UTF-8 到 ISO-8859-1 的转换有一个函数,它的名称是 utf8_decode。 - AbiusX
@deceze,这不是关于字符集转换的问题。 - mirabilos

4

我也遇到了同样的问题。将字体从otf格式转换为ttf格式可以解决这个问题。你可以使用FontForge(可在标准存储库中获得)进行转换。


这个评论让我省了很多调试时间。如果你不想使用FontForge,这是一个提示:http://www.freefontconverter.com/ - Eirik H
这里显然是最好的解决方案! - Aurélien Grimpard

0

我的主要嫌疑人是你用于渲染的字体。

根据http://fr3.php.net/imagettftext,php使用的GD库的不同版本可能会显示不同的行为。

  • 您本地机器上的GD版本:2.0或更高版本
  • 您的Web主机服务器上的GD版本:捆绑(2.0.34兼容)

编辑: 另一个想法:您能验证$text = '日本語';在您的生产服务器上是否真的保存成这样吗?也许您的脚本存在编码问题。

下一步编辑: BKB已经提出了这个问题。所以如果这是原因:他是第一个回答的;-)


-1

你的生产机器上是否存在那个特定的字体文件?如果使用FTP上传文件,你是否使用二进制编码?


1
这是一条注释,而不是对问题的回答,因此会被投票否定。 - mirabilos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接