Smarty 內建 truncate 修飾符按位元組截取,對純英文無影響,但中文 UTF-8 每字 3 位元組、GBK 每字 2 位元組,直接截取極易產生亂碼;且中英文視覺寬度差異大(一中文字≈兩英文字),定位元組數截取導致顯示長度參差不齊。本文提供完整解決方案:smartTruncate 自訂修飾符,支援 UTF-8/GBK 自動偵測,按視覺寬度(中文=1.0、英文=0.5)精準截取。
<?php
// 放入 smarty/plugins/modifier.smartTruncate.php
function smartDetectUTF8($string) {
static $result = array();
$key = md5($string);
if (!isset($result[$key])) {
$utf8_regex = '/^(?:[\x09\x0A\x0D\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})+$/xs';
$result[$key] = (bool)preg_match($utf8_regex, $string);
}
return $result[$key];
}
function smartStrlen($string) {
$len = 0;
$bytes_per_char = smartDetectUTF8($string) ? 3 : 2;
for ($i = 0; $i < strlen($string); $i += $bytes) {
$bytes = (ord($string[$i]) > 127) ? $bytes_per_char : 1;
$len += ($bytes > 1) ? 1.0 : 0.5;
}
return $len;
}
function smartSubstr($string, $start, $length = null) {
$result = '';
$bytes_per_char = smartDetectUTF8($string) ? 3 : 2;
// 處理負數 start
if ($start < 0) {
$start = max(smartStrlen($string) + $start, 0);
}
// 定位起始位置
for ($i = 0; $i < strlen($string); $i += $bytes) {
if ($start <= 0) break;
$bytes = (ord($string[$i]) > 127) ? $bytes_per_char : 1;
$start -= ($bytes > 1) ? 1.0 : 0.5;
}
// 截取
if (is_null($length)) {
$result = substr($string, $i);
} else {
for ($j = $i; $j < strlen($string); $j += $bytes) {
if ($length <= 0) break;
$bytes = (ord($string[$j]) > 127) ? $bytes_per_char : 1;
if ($bytes > 1) {
if ($length < 1.0) break;
$result .= substr($string, $j, $bytes);
$length -= 1.0;
} else {
$result .= substr($string, $j, 1);
$length -= 0.5;
}
}
}
return $result;
}
function smarty_modifier_smartTruncate($string, $length = 80, $etc = '...', $break_words = false, $middle = false) {
if ($length == 0) return '';
if (smartStrlen($string) > $length) {
$length -= smartStrlen($etc);
if (!$break_words && !$middle) {
$string = preg_replace('/\s+?(\S+)?$/', '', smartSubstr($string, 0, $length + 1));
}
if (!$middle) {
return smartSubstr($string, 0, $length) . $etc;
} else {
return smartSubstr($string, 0, $length/2) . $etc . smartSubstr($string, -$length/2);
}
}
return $string;
}
{* 模板中直接使用 *}
{$content|smartTruncate:5:".."}
{* 假設 $content = "A中B華C人D民E共F和G國H" *}
{* 輸出:A中B華C.. *}
{* 說明:中文視覺長度 1.0,英文 0.5,扣除尾碼 ".." (1.0) 後,實際截取視覺長度 4.0 *}
truncate 所有參數(尾碼、單字邊界、中間截取)。在 PHP 邏輯層預先處理,再傳給模板:
function mb_truncate($str, $width, $etc = '...') {
$width -= mb_strwidth($etc);
if (mb_strwidth($str) > $width) {
return mb_strimwidth($str, 0, $width, $etc, 'UTF-8');
}
return $str;
}
$smarty->assign('short_title', mb_truncate($long_title, 30));
或使用 mb_substr 搭配 mb_strwidth 自行封裝。