2013年9月3日 星期二

PHP函數 - 切割 utf8 字串 - 指定每行字數與行數


使用 php 時,常會從資料庫動態擷取文字。在多國語言的情況下,會有各國文字,編碼使用 UTF8,這樣就會需要特別計算文字長度或佔用的格數,方便網頁排版。

基本上,中日韓文字:長度3,佔用2格;西歐文字。長度2,佔用1格;英文:長度1,佔用1格。可參考下圖


來源:維基百科的 "UTF-8"
會計算網段的人會覺得數字好熟悉啊。都是那些數字, 128, 224, 240...。不過 192 怎麼被跳過了?這我也不知道(抓頭)。

我需要一個可以自動擷取固定位數的函數。網路上有很多這類函數,不過看來看去都不符合我需要的。大部份都是把一個字串按照指定長度切割。如果是中文的話也好處理。中文是方塊字,碰到邊了就可以換下一行。但我反倒不是要處理中文。

我經手的網站是多國網站,捷克、葡萄牙、西班牙、法國等語言。然後網頁上有一個小方塊。這個地方的跑馬燈,每一筆都要有兩行。有時候如果單字比較短,會只有一行。如果遇到比較長的單字,該方塊又會被擠成三行。

舉例來說,如果限制總長度是 50 像素,一行平均25像素,所以大概 1 行 3 個單字吧。但是有時候第 1 行的單字比較長,於是被瀏覽器自動擺到第 2 行,因此第 1 行可能只用了 20 像素。第 2 行也只用 20 像素。那剩下的就被擠到第 3 行了。但是絕不能出現第 3 行。因為這樣會讓網頁版面切不齊。這個網頁就像九宮格的拼圖那樣。這根本是版面設計不佳吧。

因此我需要一個函數,可以把字串切割成數行。每一行的長度就是指定的長度,超過這個長度,就算是把英文單字從中腰斬也要切。既然找不到,就自己寫一個。
<?php
//切割 utf8 字串:指定每行字數與行數 by Ron
function utf8_str_split($str, $split_len, $arr_num){ //字串、切割長度、取幾行
    $strLen = strlen($str); //字串長度
    $n = 0; //第幾陣列
    $nStrLen = 0;  //某一陣列累積長度(byte)
    $nShowLen = 0; //某一陣列累積顯示長度
    $currStrLenPos = 0; //以整個字串而言,當前計算的長度位置 (byte)
    $currChrNum = 0; //第幾個字
    
    //每次處理一個字元。
    while($currStrLenPos < $strLen){
        $currChr = mb_substr($str,$currChrNum,1,'utf8'); //當前字元
        $startPos = ord($currChr);
        if($startPos < 128){
            $thisChrLen = 1; //字串長度
            $thisChrShowLen = 1; //顯示長度
        }
        elseif($startPos < 192){
            //no data
        }
        elseif($startPos < 224){
            $thisChrLen = 2;
            $thisChrShowLen = 2;
        }
        elseif($startPos < 240){
            $thisChrLen = 3;
            $thisChrShowLen = 2;
        }
        elseif($startPos < 248){
            $thisChrLen = 4;
            $thisChrShowLen = 2;
        }
        elseif($startPos < 252){
            $thisChrLen = 5;
            $thisChrShowLen = 2;
        }
        elseif($startPos < 254){
            $thisChrLen = 6;
            $thisChrShowLen = 2;
        }
        $nStrLen += $thisChrLen;
        $nShowLen += $thisChrShowLen;
        $currStrLenPos += $thisChrLen;
        $currChrNum ++;
  
        //再加一個字就超過切割長度(取得一個陣列元素) || 已達字串結尾
        if(($nShowLen + $thisChrShowLen) > $split_len || ($currStrLenPos > $strLen)){
  
            if(!empty($nLenPosEnd)){
                $nLenPosStart = $nLenPosEnd+1; //取得陣列的起始位置。上一次陣列結束的地方加1
            }
            $nLenPosEnd = $currStrLenPos-1; //取得陣列的結束位置
            $array[$n] = substr($str, $nLenPosStart, $nStrLen);
            
            //已取得最後一個陣列 || 計算過的位置已達字串長度  => 取得最終結果。
            if(($n+1)==$arr_num || ($currStrLenPos+1)>$strLen){
                if(($strLen-$currStrLenPos)>0){
                    $array[$n] = substr($array[$n],0,-3).'...';
                }
                break;
                //$n 從 0 開始,加 1 才會跟 $arr_num(需要的行數) 相等。
            }
            $nStrLen = 0;
            $nShowLen = 0;
            $n++;
        }
    }
    return implode('<bBR>', $array);
}

$str = '一二三四五六七八九零一二三四五六七八九零一二三四五六七八九零一二三四五六七八九零一二三四五六七八九零';
$str = utf8_str_split($str, 12, 3);
echo "$str<BR><BR>"; 

$str = 'dog cat chicken pig cow horse rabbit sheep mouse apple orange Banana Coconut ';
$str = utf8_str_split($str, 12, 3);
echo "$str<BR><BR>"; 
?>
註:為了讓程式碼在網誌裡正確顯示,函數結尾那個 < BR> 有多空一格。要注意

結果:

一二三四五六
七八九零一二
三四五六七...

dog cat chic
ken pig cow
horse rab...

2014-05-28 註:
剛剛發現這篇 2009 年的文章,使用 php 內建的 unpack 函數,好像也很好用。
PHP 依固定長度切割字串(unpack)

參考
PHP中文字符判断
在UTF-8的編碼下縮減字串... (中文說明比較多)

沒有留言:

張貼留言