substr、`mb_substr` 按**字元數**截取,但中文通常視為「全形」佔兩格寬度,英文「半形」佔一格。若需在固定寬度版面(如列表摘要、meta description)顯示,必須依**視覺寬度**截斷。本文提供 cutstr 函式,支援 UTF-8、自動處理 HTML 實體、保留尾部省略符號。
function cutstr(string $string, int $length, string $dot = '...'): string
// $length 為視覺寬度上限(中文算 2,英文/數字/符號算 1)
<?php
define('CHARSET', 'UTF-8');
function cutstr(string $string, int $length, string $dot = '...'): string
{
if (mb_strlen($string, CHARSET) <= $length) {
return $string;
}
$pre = chr(1);
$end = chr(1);
// 保護 HTML 實體
$string = str_replace(
['&', '"', '<', '>'],
[$pre . '&' . $end, $pre . '"' . $end, $pre . '<' . $end, $pre . '>' . $end],
$string
);
$strcut = '';
if (strtolower(CHARSET) === 'utf-8') {
$n = $tn = $noc = 0;
$len = strlen($string);
while ($n < $len) {
$t = ord($string[$n]);
// ASCII 可列印字元(含空白、換行、Tab)
if ($t === 9 || $t === 10 || (32 <= $t && $t <= 126)) {
$tn = 1; $n++; $noc++;
}
// 2 bytes: 194-223
elseif (194 <= $t && $t <= 223) {
$tn = 2; $n += 2; $noc += 2;
}
// 3 bytes: 224-239 (中日韓大多在這區)
elseif (224 <= $t && $t <= 239) {
$tn = 3; $n += 3; $noc += 2;
}
// 4 bytes: 240-247
elseif (240 <= $t && $t <= 247) {
$tn = 4; $n += 4; $noc += 2;
}
// 5 bytes: 248-251
elseif (248 <= $t && $t <= 251) {
$tn = 5; $n += 5; $noc += 2;
}
// 6 bytes: 252-253
elseif ($t === 252 || $t === 253) {
$tn = 6; $n += 6; $noc += 2;
} else {
$n++; // 忽略無效位元組
}
if ($noc >= $length) break;
}
if ($noc > $length) $n -= $tn;
$strcut = substr($string, 0, $n);
} else {
// 非 UTF-8 退回簡易雙位元組處理(Big5/GBK 等)
for ($i = 0; $i < $length; $i++) {
$strcut .= (ord($string[$i]) > 127) ? $string[$i] . $string[++$i] : $string[$i];
}
}
// 還原 HTML 實體
$strcut = str_replace(
[$pre . '&' . $end, $pre . '"' . $end, $pre . '<' . $end, $pre . '>' . $end],
['&', '"', '<', '>'],
$strcut
);
// 移除殘留控制字元
$pos = strrpos($strcut, chr(1));
if ($pos !== false) $strcut = substr($strcut, 0, $pos);
return $strcut . $dot;
}
$text = 'PHP 字串截取測試:Hello World!';
echo cutstr($text, 20);
// 輸出:PHP 字串截取測試:Hel... (視覺寬度約 20)
$html = '標題:<strong>重要</strong>內容';
echo cutstr($html, 15);
// 輸出:標題:<strong>重... (實體保持完整)
若專案允許安裝擴充套件,推薦使用 mbstring 搭配 grapheme_strlen 或 IntlChar::charWidth 更精確處理 Emoji、合成字元、零寬連接符等複雜情況:
// 簡易版:mb_substr 按字元數截取(非視覺寬度)
function cutstr_mb(string $str, int $len, string $dot = '...'): string
{
return mb_strlen($str) > $len ? mb_substr($str, 0, $len) . $dot : $str;
}