專案需依標題像素寬度截斷並加上「...」。初期使用 mb_strimwidth()、mb_strwidth(),發現中文雙引號「 」被判定為寬度 1(半形),實際顯示卻為全形寬度,導致排版錯位。
mb_strwidth() 依據 East Asian Width 屬性判定,該區塊字元預設視為半形(寬度 1)逐字檢查,遇 U+2000–U+206F 區塊字元強制計為寬度 2,其餘沿用 mb_strwidth()。
<?php
/**
* 修正版字串寬度計算:將 U+2000–U+206F 視為全形寬度 2
*/
function amb_strwidth(string $str): int {
$count = 0;
$len = mb_strlen($str);
for ($i = 0; $i < $len; $i++) {
$ch = mb_substr($str, $i, 1);
// 正則判斷是否落在 General Punctuation 區塊
if (preg_match('/[\x{2000}-\x{206F}]/u', $ch)) {
$count += 2;
} else {
$count += mb_strwidth($ch);
}
}
return $count;
}
/**
* 依指定寬度截斷字串並補上省略號
*/
function truncString(string $str, int $width): string {
$count = 0;
$len = mb_strlen($str);
for ($i = 0; $i < $len; $i++) {
$ch = mb_substr($str, $i, 1);
$count += amb_strwidth($ch);
if ($count > $width) {
return mb_substr($str, 0, $i) . '...';
}
}
return $str;
}
// 測試
$title = '「PHP 開發」實戰指南';
echo '原始寬度: ' . mb_strwidth($title) . PHP_EOL; // 可能偏小
echo '修正寬度: ' . amb_strwidth($title) . PHP_EOL; // 正確
echo '截斷結果: ' . truncString($title, 20) . PHP_EOL; // 依視覺寬度截斷
?>
mb_strpos 或預編譯字元對應表,避免逐字 preg_match 開銷。StringWidth::get($str)、StringWidth::truncate($str, $width) 供專案統一呼叫。mb_strwidth() 依 Unicode 標準實作,但實際字型渲染常有出入。針對專案常用標點區塊(如 U+2000–U+206F)客製化修正,能有效解決中文排版截斷錯位問題。