LoginSignup
9
10

More than 5 years have passed since last update.

mb_strimwidthの罠

Posted at

全角を2,半角を1として数え、指定した幅で切り取ってくれる便利なmb_strimwidth()ですが、以下のように数え間違えに見える罠が存在します。

mb_strimwidth("123456", 0, 5, "…", "UTF-8"); // 1234…
mb_strimwidth("…123456", 0, 5, "", "UTF-8"); // …1234

それぞれ「123…」「…123」になって欲しいのです。

mb_strwidth単体で確認すると3点リーダが半角1文字として数えられてることがわかります。

mb_strwidth("A", "UTF-8"); // 1
mb_strwidth("あ", "UTF-8"); // 2
mb_strwidth("…", "UTF-8"); // 1

何故?

これはUnicode附属書#11で3点リーダ(U+2026)の幅はコンテキストによってA(Ambiguous; 曖昧)と定義され、さらにコンテキストが分からない場合は狭い文字として扱えと指示されています。
PHPでもそれに従おうという経緯があり、3点リーダなどは1として数えられるようになったようです。

実際自分の環境ではQiitaでの表示も「A」と同じ幅になっており間違いと言い切れないことが分かります。

でも困る

こういった文字長で切る処理はWebの場合はCSSで対応すれば楽ですが、そうでない帳票などの場合に困ります。
しかもそういった場合は大抵MSゴシックといった「…」を全角で表現するフォントが使われ、件の数え方では不都合が生じます。

代替案

その場合の代替案としてmb_strcutをSJIS文字列に対して適用するのが簡単だと思います。
mb_strcutは全角半角の幅ではなくマルチバイト境界を認識しつつバイト数で切ってくれます。
そこで「…」など全角になって欲しい文字が2バイト、半角カナが1バイトになるSJISエンコーディングを使えば都合が良いというわけです。

その理屈を使ってmb_strimwidthを実装しなおすとこんな感じでしょうか。

function mb_strimwidth2($str, $start, $width, $trimmarker="", $encoding=null) {
    if (is_null($encoding)) {
        $encoding = mb_internal_encoding();
    }
    $str = mb_substr($str, $start, null, $encoding);
    $str_ = mb_convert_encoding($str, "SJIS-win", $encoding);
    $trimmarker_ = mb_convert_encoding($trimmarker, "SJIS-win", $encoding);
    if (strlen($str_) > $width) {
        $result = mb_strcut($str_, 0, $width - strlen($trimmarker_), "SJIS-win") . $trimmarker_;
        return mb_convert_encoding($result, $encoding, "SJIS-win");
    } else {
        return $str;
    }
}

mb_strimwidthの動きに合わせてみましたが、実際の場面ではSJISだったりすると思うのでmb_convert_encodingなどは使わないかも知れません。
また、$startパラメータの扱いがmb_strcutではバイト、mb_strimwidthでは文字数だったので合わせるためmb_substr使ってますがこれも0を指定するなら要らない所です。
(mb_strimwidth$startが文字幅じゃないというのはそれはそれで罠かも知れません。)

ちなみに

1とか2ではなくプロポーショナルフォントにも対応した本当の文字幅が欲しい時はTTFフォントで文字描画する機能を持った画像orPDFライブラリを使うことになると思います。
「bounding box」「metrics」「measure」的な単語の入ってるメソッドが大抵あります。

9
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10