iT邦幫忙

1

php mb_substr & substr

  • 分享至 

  • xImage

在php中,截取部份字元可以用
mb_substr($prodcname,0,20,"UTF-8")

substr($prodcname,0,20)

在印報表時,假設某個欄位可以放中文 20 字或英文 40 個字,在純中文字或純英文字時,可以決定使用那個函數,但遇到中英夾雜時就很麻煩了。

當使用 mb_substr($prodcname,0,20,"UTF-8"),遇到全英文時,該欄只印了一半,空了一大塊。

若使用 substr($prodcname,0,20)時,碰到中文時又會印出格子外。

有沒有兩全其美的方法可以充分利用欄寛限制?儘量印好印滿!

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 個回答

2
淺水員
iT邦大師 6 級 ‧ 2020-09-11 01:12:26
最佳解答
/** 
 * 依據寬度擷取字串
 *
 * @param string $str 原始 utf8 字串
 * @param int $width 寬度,unocide<128時寬度為1,其餘為2
 * @return string 不大於 $width 的最長子字串
 */
function getSubStringByWidth($str, $width) {
    $n=strlen($str);
    $k=0;
    while($k<$n) {
        $c=ord($str[$k++]);
        if($c<128) {
            $r=0; //後面還要讀取幾個 byte
            $len=1; //這個字的寬度
        } elseif(($c&0xe0)===0xc0) {
            $r=1;
            $len=2;
        } elseif(($c&0xf0)===0xe0) {
            $r=2;
            $len=2;
        } elseif(($c&0xf8)===0xf0) {
            $r=3;
            $len=2;
        } else {
            throw new \Exception('bad utf8 encoding');
        }
        if($len>$width) {
            --$k;
            break;
        }
        $width-=$len;
        //後面要接 r個 10xx xxxx
        for(;$r>0 && $k<$n;--$r) {
            $c=ord($str[$k++]);
            if(($c&0xc0)!==0x80) {
                break;
            }
        }
        if($r>0) {
            throw new \Exception('bad utf8 encoding');
        }
    }
    return substr($str, 0, $k);
}

參考:

ckp6250 iT邦好手 1 級 ‧ 2020-09-11 11:20:27 檢舉

十分感激,
本來以為很簡單,沒想到這麼複雜。
我還要仔細研究一番。

1

以下是我以前自已寫的,給你參考使用。

不過我這寫法遇到起始或截斷的地方剛好是中文。有機會會多一格。
如果你想少一格的話。就將「//取得長度足夠時,跳出」下面那斷判斷式移到「//是否超過起始位置」上面就好。
我是覺得多一點還沒關係。

另外,這只適合utf8。我當時懶的寫編碼判斷了。反正是要自用的。
這是將中文字視為2格寬度。英文字視為1格寬度。
不過那時因為排板的問題。英文字明顯寬度會小很多。
所以可以調整eLen或eLen值。來做微調處理。

function hstar_utf8substr($str, $start, $len)
    {
        $l = 0;         //字串指標位置
        $addStr = 0;    //字串開始加入
        $getLen = 0;    //已取得的長度
        $cLen=2;        //中文計算長度(寬度)
        $eLen=1;        //英數計算長度(寬度)
        $unLen = strlen($str);        
        $getStr = '';
        for($lc=0;$lc<$unLen;$lc++){
            $tempStr = substr($str,$lc,3);
            if(mb_strlen($tempStr)==1){
                //這個是中文,計算寬度2                
                $lc += 2;
                $getLen +=$cLen;
            }else{
                //這不是中文,重取一格
                $tempStr = substr($str,$lc,1);                
                $getLen +=$eLen;
            }

            //是否超過起始位置
            if($getLen>=$start){
                $getStr.=$tempStr;
            }
            //取得長度足夠時,跳出
            if($getLen-$start >= $len){
                break;
            }
        }
        return $getStr;
    }
看更多先前的回應...收起先前的回應...
淺水員 iT邦大師 6 級 ‧ 2020-09-11 11:21:01 檢舉

不過那時因為排板的問題。英文字明顯寬度會小很多。

這主要跟字型有關,只有等寬字型,英文字才會剛好是中文的一半。
(一般程式編輯器顯示畫面,用的就是等寬字型)
如果不是等寬字型,英文往往會有一些調整,會比較好看。

嗯,我那時其實是將英文字調+0.8。這樣會整體寬度+-值不會超過40px
要不然差異40px。一整列下來很難看。

另外再說一件事。其實我曾經有過一次,是連英數也要分離的。
因為英文跟數字的寬度有些許誤差。英文字寬度較小,但數字則沒變。
但客戶就是喜歡用這個字型。

只好再做英數的判斷了。
不過那個我就沒保留了。那是我後期改的。

ckp6250 iT邦好手 1 級 ‧ 2020-09-11 11:47:06 檢舉

十分感恩啊,
程式碼非常精巧,我很喜歡。

ckp6250 iT邦好手 1 級 ‧ 2020-09-11 11:50:10 檢舉

若是用細明體的話,中文應該就會是英數的2倍了,視覺比例上可能比較剛好。

跟字體比較沒關係,跟字型檔比較有關係。
淺水員有說了,如果選等寬字型檔才會是剛好二倍。
怕的不是等寬的才頭痛。

淺水員 iT邦大師 6 級 ‧ 2020-09-11 12:16:56 檢舉

要精細計算的話,我可能會用 fontkit 去分析字型裡面每個字的寬度
預先做成對照表(其實只有英數而已,其他全形字大小都是一樣的)

下面這程式碼可以看出某個字型檔每個字的寬度
(主要是半形字,全形字我隨便找幾個測試而已)

const fontkit = require('fontkit');
const path = require('path');

var fontFile=path.resolve(__dirname, './SourceHanSansTW-Light.otf');
var font = fontkit.openSync(fontFile);

console.log(`unitsPerEm: ${font.unitsPerEm}`);
for(let code=0; code<128; ++code) {
    if(!font.hasGlyphForCodePoint(code)) {
        continue;
    }
    glyph = font.glyphForCodePoint(code);
    console.log(`${code} ${String.fromCodePoint(code)} ${glyph.advanceWidth}`);
}

let s='丨川, '; //全形字測試
Array.from(s).forEach((c)=>{
    let code=c.codePointAt();
    if(!font.hasGlyphForCodePoint(code)) {
        return;
    }
    glyph = font.glyphForCodePoint(code);
    console.log(`${code} ${String.fromCodePoint(code)} ${glyph.advanceWidth}`);
});

哈哈,果然也是用同一招。
雖然不太相同。我之前是用gd函數計算過。但無法很精準。
某些字型就是很他xx的怪。且也很操效能。
不過想說反正只計算一次也沒差。

但碰到很頭大的字型,gd不吃或ttf不吃。就有點頭大。
後來是自已用了js跑計算寬度儲存下來用的。用js計算出來的會比較精準。都是滿滿的。

我要發表回答

立即登入回答