微博字數規則:中文/全形算 2 字元,英文/半形算 1 字元。標準 `strlen` 計算位元組數 (UTF-8 中文 3 位元組),`mb_strlen` 計算字元數 (中英皆算 1),均不符合需求。本文提供支援 UTF-8/GBK 的精確寬度計算演算法。
| 類別 | 範例 | 寬度 | Unicode 範圍 |
|---|---|---|---|
| ASCII 列印字元 | a-z A-Z 0-9 !@#... | 1 | U+0020 - U+007E |
| 控制字元 | Tab, LF, CR | 1 | U+0009, U+000A, U+000D |
| 全形 ASCII | ABC 123 !@# | 2 | U+FF01 - U+FF5E |
| 中日韓統一表意文字 | 中 文 測 試 | 2 | U+4E00 - U+9FFF 等 |
| 其他 CJK 符號 | 。 , 、 ; : ? | 2 | U+3000 - U+303F |
| Emoji / 輔助平面 | 😀 🎉 👍 | 2 | U+1F000+ |
<?php
/**
* 微博式字元寬度計算 (支援 UTF-8)
*
* @param string $string 輸入字串
* @param string $charset 編碼 (utf-8, gbk, gb2312, big5)
* @return int 字元寬度總和
*/
function strlen_weibo($string, $charset = 'utf-8') {
$n = $count = 0;
$length = strlen($string);
$charset = strtolower($charset);
if ($charset === 'utf-8') {
while ($n < $length) {
$byte = ord($string[$n]);
// ASCII 列印字元 + 控制字元 (寬度 1)
if ($byte === 9 || $byte === 10 || (32 <= $byte && $byte <= 126)) {
$n++;
$count++;
}
// 2 位元組 UTF-8 序列 (U+0080 - U+07FF) - 稀有字元,寬度 2
elseif (194 <= $byte && $byte <= 223) {
$n += 2;
$count += 2;
}
// 3 位元組 UTF-8 序列 (U+0800 - U+FFFF) - 中文主平面,寬度 2
elseif (224 <= $byte && $byte <= 239) {
$n += 3;
$count += 2;
}
// 4 位元組 UTF-8 序列 (U+10000 - U+10FFFF) - Emoji/輔助平面,寬度 2
elseif (240 <= $byte && $byte <= 247) {
$n += 4;
$count += 2;
}
// 5 位元組 (理論上 UTF-8 不超過 4 位元組,保留相容)
elseif (248 <= $byte && $byte <= 251) {
$n += 5;
$count += 2;
}
// 6 位元組 (同上)
elseif ($byte === 252 || $byte === 253) {
$n += 6;
$count += 2;
}
// 非法位元組序列,按 1 處理
else {
$n++;
$count++;
}
// 防禦性檢查
if ($count >= $length) break;
}
return $count;
}
// GBK/GB2312/Big5: 位元組值 > 127 視為雙位元組字元 (寬度 2)
else {
for ($i = 0; $i < $length; $i++) {
if (ord($string[$i]) > 127) {
$i++; // 跳過第二位元組
$count++; // 雙位元組算寬度 2,這裡先 +1,下面再 +1
}
$count++;
}
return $count;
}
}
// 測試
$test = "s@@你好"; // s(1) + 全形@(2) + 半形@(1) + 你(2) + 好(2) = 8
echo "字串: {$test}\n";
echo "寬度: " . strlen_weibo($test, 'utf-8') . "\n"; // 輸出: 8
?>
位元組 1 範圍 位元組數 Unicode 範圍 典型字元 微博寬度 0xxxxxxx (0-127) 1 U+0000-U+007F ASCII 1 110xxxxx (192-223) 2 U+0080-U+07FF 希臘/西里爾/阿拉伯 2 1110xxxx (224-239) 3 U+0800-U+FFFF 中日韓主平面 2 11110xxx (240-247) 4 U+10000-U+10FFFF Emoji/古文字 2 111110xx (248-251) 5 (非標準 UTF-8) - 2 1111110x (252-253) 6 (非標準 UTF-8) - 2
<?php
// 方案 1: grapheme_strlen (計算使用者感知字元數,非寬度)
$str = "s@@你好👍";
echo "Grapheme: " . grapheme_strlen($str) . "\n"; // 6 (s, @, @, 你, 好, 👍)
// 方案 2: IntlChar::charWidth (需 intl 擴充,PHP 8.1+)
function strlen_weibo_modern($str) {
$width = 0;
$graphemes = grapheme_str_split($str); // PHP 8.0+
foreach ($graphemes as $char) {
$cp = IntlChar::ord($char);
// 東亞寬度判斷 (近似)
if (IntlChar::charType($cp) === IntlChar::CHAR_CATEGORY_OTHER_SYMBOL ||
($cp >= 0x1100 && $cp <= 0x115F) || // Hangul Jamo
($cp >= 0x2E80 && $cp <= 0xA4CF) || // CJK 範圍
($cp >= 0xAC00 && $cp <= 0xD7A3) || // Hangul Syllables
($cp >= 0xF900 && $cp <= 0xFAFF) || // CJK Compatibility
($cp >= 0xFE10 && $cp <= 0xFE1F) || // Vertical forms
($cp >= 0xFE30 && $cp <= 0xFE6F) || // CJK Compatibility Forms
($cp >= 0xFF00 && $cp <= 0xFF60) || // Fullwidth Forms
($cp >= 0x1F000 && $cp <= 0x1FFFF) // Emoji 等
) {
$width += 2;
} else {
$width += 1;
}
}
return $width;
}
// 方案 3: mbstring 寬度模式 (PHP 7.2+ mb_strwidth)
echo "mb_strwidth: " . mb_strwidth($str, 'UTF-8') . "\n"; // 直接得到寬度!
// 注意: mb_strwidth 規則與微博略有差異,需實測驗證
?>
<?php
function benchmark($fn, $str, $iterations = 10000) {
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) $fn($str);
return microtime(true) - $start;
}
$testStr = str_repeat("中文English全形@半形@", 100);
echo "strlen_weibo (自訂): " . benchmark('strlen_weibo', $testStr) . "s\n";
echo "mb_strwidth (內建): " . benchmark('mb_strwidth', $testStr) . "s\n";
/* 典型結果:
strlen_weibo: 0.45s (PHP 層迴圈)
mb_strwidth: 0.08s (C 層實作,快 5-6 倍)
*/
?>
String.prototype.codePointAt),後端驗證用 PHP<script>
function weiboLength(str) {
let width = 0;
for (let i = 0; i < str.length; i++) {
const cp = str.codePointAt(i);
// 簡易判斷:BMP 範圍內非 ASCII 視為寬度 2
// 完整版需對照 Unicode 東亞寬度表
if (cp > 0x7F && cp <= 0xFFFF) width += 2;
else if (cp > 0xFFFF) width += 2; // 輔助平面 (Emoji)
else width += 1;
// 處理代理對
if (cp > 0xFFFF) i++;
}
return width;
}
console.log(weiboLength("s@@你好👍")); // 10
</script>
掌握字元寬度計算核心邏輯,配合現代 `mb_strwidth` 或 `IntlChar`,能精確實作微博式字數限制,提升使用者體驗。