在生成圖片驗證碼、海報、水印、PDF 佈局時,需預先知道文本渲染後的像素寬高,以便居中、換行、裁剪。PHP GD 擴展提供 imagettfbbox() 返回文本邊界框,本文封裝為可複用的 text_width()/text_height() 函數,並解析坐標系含義與常見坑點。
<?php
// 配置常量
define('FONT_SIZE', 14); // 字體大小(pt)
define('FONT_FILE', __DIR__ . '/fonts/arial.ttf'); // TrueType 字體路徑
define('FONT_ANGLE', 0); // 旋轉角度(度)
/**
* 獲取文本邊界框(8 個坐標點)
*
* @return array [0]x0 [1]y0 [2]x1 [3]y1 [4]x2 [5]y2 [6]x3 [7]y3
* 順序:左下、右下、右上、左上(逆時針)
*/
function getTextBBox(string $text): array
{
$bbox = @imagettfbbox(FONT_SIZE, FONT_ANGLE, FONT_FILE, $text);
if ($bbox === false) {
throw new RuntimeException("imagettfbbox 失敗:字體文件不存在或文本包含無效字符");
}
return $bbox;
}
/**
* 計算文本寬度(像素)
*/
function textWidth(string $text): int
{
$bbox = getTextBBox($text);
// x 座標:右上(4) - 左上(6) 或 右下(2) - 左下(0)
return (int)ceil($bbox[4] - $bbox[6]);
}
/**
* 計算文本高度(像素)
*/
function textHeight(string $text): int
{
$bbox = getTextBBox($text);
// y 座標:左下(1) - 左上(7) 注意 GD Y 軸向下
return (int)ceil($bbox[1] - $bbox[7]);
}
/**
* 一次性獲取寬高
*/
function textSize(string $text): array
{
$bbox = getTextBBox($text);
return [
'width' => (int)ceil($bbox[4] - $bbox[6]),
'height' => (int)ceil($bbox[1] - $bbox[7]),
];
}
// ========== 使用示例 ==========
$text = 'Hello PHP GD!';
$size = textSize($text);
echo "文本:$text\n";
echo "寬度:{$size['width']}px\n";
echo "高度:{$size['height']}px\n";
// 實際繪製驗證
$im = imagecreatetruecolor($size['width'] + 20, $size['height'] + 20);
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
imagefilledrectangle($im, 0, 0, imagesx($im), imagesy($im), $white);
// imagettftext 以 baseline 左下角為基準
imagettftext($im, FONT_SIZE, FONT_ANGLE, 10, 10 + $size['height'], $black, FONT_FILE, $text);
imagepng($im, __DIR__ . '/text_preview.png');
imagedestroy($im);
echo "預覽圖已生成:text_preview.png\n";
?>
函數返回 8 個浮點數,對應文本邊界框 4 個角的 (x, y) 座標,順序為逆時針:
| 索引 | 含義 | 說明 |
|---|---|---|
| 0 | 左下角 X | baseline 起始點 |
| 1 | 左下角 Y | baseline Y,GD 座標系 Y 向下 |
| 2 | 右下角 X | |
| 3 | 右下角 Y | |
| 4 | 右上角 X | 最右邊界 |
| 5 | 右上角 Y | |
| 6 | 左上角 X | 最左邊界(通常為負值,含左側間距) |
| 7 | 左上角 Y | 最頂邊界 |
寬度 = max(x) - min(x) = $bbox[4] - $bbox[6]
高度 = max(y) - min(y) = $bbox[1] - $bbox[7](因 Y 向下,左下 Y 最大,左上 Y 最小)
__DIR__ . '/fonts/...' 最穩妥。SimHei.ttf、NotoSansSC-Regular.otf),否則會出現方塊/亂碼。imagettftext() 的 y 座標是 baseline,繪製時需加上 textHeight() 才能從頂部開始。<?php
/**
* 多行文本自動換行繪製
*
* @param resource $im 圖片資源
* @param string $text 文本
* @param int $x 起始 X
* @param int $y 起始 Y (baseline)
* @param int $maxWidth 最大寬度
* @param int $lineHeight 行高倍數 (建議 1.4-1.6)
* @return int 實際佔用高度
*/
function drawTextWrapped($im, string $text, int $x, int $y, int $maxWidth, float $lineHeight = 1.5): int
{
$words = preg_split('/\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY);
$lines = [];
$currentLine = '';
foreach ($words as $word) {
$testLine = $currentLine ? "$currentLine $word" : $word;
$w = textWidth($testLine);
if ($w <= $maxWidth) {
$currentLine = $testLine;
} else {
$lines[] = $currentLine;
$currentLine = $word;
}
}
if ($currentLine) $lines[] = $currentLine;
$singleH = textHeight('測');
$lineSpacing = (int)($singleH * $lineHeight);
$totalH = 0;
foreach ($lines as $line) {
imagettftext($im, FONT_SIZE, 0, $x, $y + $totalH, $black, FONT_FILE, $line);
$totalH += $lineSpacing;
}
return $totalH;
}
?>
imagettfbbox() 是 GD 文本渲染的基石,掌握其返回坐標含義並封裝為 textWidth()/textHeight(),即可輕鬆實現文本居中、自動換行、圖文混排等高級佈局。記得處理字體路徑、授權、中文支持與 Baseline 對齊,即可在圖片生成、驗證碼、海報合成等場景遊刃有餘。