[PHP-dev 1381] Re: mb_strwidth関数とmb_strimwidth関数の挙動について

Moriyoshi Koizumi moriyoshi @ at.wakwak.com
2007年 8月 9日 (木) 14:35:31 JST


小泉です。

# すみません、そこを変更したのは私です、ということで。
きちんと議論を把握していないので的外れなことを言っていたらすみません。以
下参考になれば。

まずどうして現在の様な挙動なのかを説明したいと思います。

かつては、JISX0208 や JISX0213 に含まれる文字はいわゆる「全角」で、幅が
いわゆる「ANK」文字の 2 倍とする実装を採用していました。ですが、これでは
ハングルや簡体字中国語などの文字をサポートできていないという問題があり、
実際それに関するバグ報告が数件ある状態でした。

もともと文字列の幅というものを文字コードの文脈で示すのは無理があるという
ことを踏まえつつ、広く知られており、国際的に認知されている定義として
Unicode のものを採用するという結論に至りました。もちろん、それを好しとす
るわけではありません。

互換性に関しては、mb_strimwidth は「文字列の幅を元に文字列を切り出す」処
理を行うものとあり、一方で mb_strcut は「文字列の長さ (バイト数) を元に
切り出す」とあります。これまではたまたま mb_strimwidth は mb_strcut とよ
く似た挙動をしていたと考えてください。DB のカラム幅に納めるために文字列
を切り取るような処理では mb_strcut の方を使うべきです。

もし、文字列の幅をバイト数と等しいとみなして何か処理を行いたければ、私は
strlen() と mb_convert_encoding を組み合わせてやることをお薦めしたいと思
います。新たに関数を追加する必要はないと思います。

また、ini 設定で挙動が変わるようにはすべきではないと思います。


2007-08-09 (木) の 13:54 +0900 に Seiji Masugata さんは書きました:
> こんにちわ、桝形です。
> 
> http://ml.php.gr.jp/pipermail/php-users/2007-August/thread.html#33032
> 
> PHP-usersのMLで少し盛り上がっていた件ですが、その後、さわださんと
> 個人的にメールでやりとりしているうちに、なんとなく掴めてきました。
> 
> 内容の報告と今後の方向性について議論できれば、と思います。
> 
> 
> さわださんには色々と作業をして頂きました。本当にありがとうございます。 
> 本来は桝形が行うべき作業、手間を取らせてしまってスミマセンデシタ。
> 
> まずは調査の内容を報告します。結構長くなります。
> 間違っていたり事実誤認がありましたら指摘してください。
> 
> 
> 
> var_dump( mb_strwidth( '※' ) );
> 
> この結果が1になる件ですが、
> 
> 
> [ /php-src/ext/mbstring/mbstring.c ]
> PHP_FUNCTION(mb_strwidth) => mbfl_strwidth
> 
> ↓     ↓     ↓     ↓     ↓     ↓
> 
> [ /php-src//ext/mbstring/libmbfl/mbfl/mbfilter.c ]
> mbfl_strwidth => filter_count_width => is_fullwidth
> 
> という流れになっていて、処理の実体はis_fullwidth関数である事が分かりました。
> (この関数で1が返れば2の幅として扱われ、0が返れば1の幅として扱われる)
> 
> 
> 1を返す場合の判定が、
> 
> for (i = 0; i < sizeof(mbfl_eaw_table) / sizeof(mbfl_eaw_table[0]); i++) {
> 	if (mbfl_eaw_table[i].begin <= c && c <= mbfl_eaw_table[i].end) {
> 		return 1;
> 	}
> }
> 
> このようになっており、mbfl_eaw_tableに判定の実体がある事が分かりました。
> (/php-src/ext/mbstring/libmbfl/mbfl/eaw_table.h)
> 
> 
> 内部的にはUCS-2で処理されている感じがしたので、
> 
> <?php
>   declare( encoding="EUC-JP" );
>   var_dump( mb_strwidth( '※' ) );
>   var_dump( bin2hex( mb_convert_encoding( '※', "UCS-2" ) ) );
> ?>
> 
> こんな感じで16進表記を調べてmbfl_eaw_tableの定義を見てみたところ、範囲内に
> 入っていなかったので0が返る(幅が1として扱われる)事が分かりました。
> 
> ですので、この構造体に、
> 
>     { 0x203a, 0x203c },
> 
> このような定義を追加する事で期待していた結果が返るようになりました。
> (1が返り、2の幅として扱われる)
> 
> 
> 
> 他にもあるんじゃないのか、という事で、さわださんの方に調べてもらっていた
> ところ、非常に興味深い事を教えて頂きました。
> 
> http://www.mono-space.net/blog/programming/051026_php_mbfl.htm
> http://www.unicode.org/reports/tr11/#Recommendations
> ----------------------------------------------------------------------------
> When mapping Unicode to East Asian legacy character encodings
> 
>     * Wide Unicode characters always map to fullwidth characters.
>     * Narrow (and neutral) Unicode characters always map to halfwidth characters.
>     * Halfwidth Unicode characters always map to halfwidth characters.
>     * Ambiguous Unicode characters always map to fullwidth characters.
> 
> When mapping Unicode to non-East Asian legacy character encodings
> 
>     * Wide Unicode characters do not map to non-East Asian legacy character encodings.
>     * Narrow (and neutral) Unicode characters always map to regular (narrow) characters.
>     * Halfwidth Unicode characters do not map.
>     * Ambiguous Unicode characters always map to regular (narrow) characters.
> ----------------------------------------------------------------------------
> 
> 実装としては間違っていないけれど、今は常にnon-East Asianとして定義
> されているのでは、という事でした。本来であれば、該当する部分は、
> 
> - East Asian   : 幅2
> - non-East Asian : 幅1
> 
> というような分岐が必要なのでは、という事です。
> 
> 
> 
> ftp://ftp.unicode.org/Public/UNIDATA/EastAsianWidth.txt
> 
> このデータを元にmbfl_eaw_tableを生成するawkが既にあるとの事なので
> さわださんに確認して頂きました(awkを若干、修正したそうです)。
> 
> http://cvs.php.net/viewvc.cgi/php-src/ext/mbstring/libmbfl/mbfl/mk_eaw_tbl.awk?revision=1.2.2.1.2.1&view=markup
> 
> mbfl_eaw_tableを生成しなおした上でmb_strwidthの動作を確認してみたところ、
> 
> 【mb_internal_encoding=UTF-8、file-encoding=UTF-8】
> ¢ code:00a2 strlen:2 mb_strwidth:1
> £ code:00a3 strlen:2 mb_strwidth:1
> ¬ code:00ac strlen:2 mb_strwidth:1
> ÷ code:00f7 strlen:2 mb_strwidth:2
> ― code:2015 strlen:3 mb_strwidth:2
> ‖ code:2016 strlen:3 mb_strwidth:2
> ※ code:203b strlen:3 mb_strwidth:2
> − code:2212 strlen:3 mb_strwidth:1
> □ code:25a1 strlen:3 mb_strwidth:2
> ◯ code:25ef strlen:3 mb_strwidth:2
> ☆ code:2606 strlen:3 mb_strwidth:2
> 〜 code:301c strlen:3 mb_strwidth:2
>  ̄ code:ffe3 strlen:3 mb_strwidth:2
> 
> 【mb_internal_encoding=eucJP-win、file-encoding=EUC-JP】
> ¢ code:ffe0 strlen:2 mb_strwidth:2
> £ code:ffe1 strlen:2 mb_strwidth:2
> ¬ code:ffe2 strlen:2 mb_strwidth:2
> ÷ code:00f7 strlen:2 mb_strwidth:2
> ― code:2015 strlen:2 mb_strwidth:2
> ‖ code:2225 strlen:2 mb_strwidth:2
> ※ code:203b strlen:2 mb_strwidth:2
> − code:ff0d strlen:2 mb_strwidth:2
> □ code:25a1 strlen:2 mb_strwidth:2
> ◯ code:25ef strlen:2 mb_strwidth:2
> ☆ code:2606 strlen:2 mb_strwidth:2
> 〜 code:ff5e strlen:2 mb_strwidth:2
>  ̄ code:ffe3 strlen:2 mb_strwidth:2
> 
> ¢£¬−あたりが問題っぽい、との連絡を受けました。
> 
> http://www.asahi-net.or.jp/~wq6k-yn/code/jisucs.html
> ----------------------------------------------------------------------------
> セント記号
> 
> JIS漢字のセント記号(¢)はCENT SIGNである。対応するUCSのコードポイントは
> U+00A2である。ところが、これをUCSのFULLWIDTH CENT SIGNに変換するものがある。
> ASCII にもJIS X 0201にもセント記号はないので、これが「FULLWIDTH」になる
> 理由はない。従ってこの変換は不適切である。
> 
> 
> ポンド記号
> 
> JIS漢字のポンド記号(£)はPOUND SIGNである。対応するUCSのコードポイントは
> U+00A3である。ところが、これをUCSのFULLWIDTH POUND SIGNに変換するものがある。
> ASCIIにもJIS X 0201にもポンド記号はないので、これが「FULLWIDTH」になる
> 理由はない。従ってこの変換は不適切である。
> 
> 
> 否定記号
> 
> JIS漢字の否定記号(¬)はNOT SIGNである。対応するUCSのコードポイントは
> U+00ACである。ところが、これをUCSのFULLWIDTH NOT SIGNに変換するものがある。
> ASCII にもJIS X 0201にも否定記号はないので、これが「FULLWIDTH」になる
> 理由はない。従ってこの変換は不適切である。
> 
> 
> 負符号
> 
> JIS漢字の負符号(−)はMINUS SIGNである。対応するUCSのコードポイントは
> U+2212である。これをUCSのFULLWIDTH HYPHEN-MINUSに変換するものがあるが、
> このような変換は、文字集合の体系としての理解に基づいているとはいえない。
> 
> JIS漢字においては、負符号とハイフン(‐)とは明確に分離されており、紛れが
> ない。一方、HYPHEN-MINUSはASCIIの「-」のことで、ハイフンにも負符号にも
> 使われる、意味の曖昧な記号である。JIS漢字にHYPHEN-MINUSは存在しない。
> よって上のような変換は不適切である。
> ----------------------------------------------------------------------------
> 
> さわださんの話では、使い勝手としては無理矢理幅2にしてしまった方が
> 良いかもしれない、との事ですが、ここは慎重を要するかもしれません。
> 
> 
> 以上がさわださんと個人的にメールでやりとりしていた内容になります。
> 
> 
> 
> 個人的には(¢£¬−の件は、ひとまず横に置いておいて。。。)後方
> 互換性を壊す事はしたくないので、新たに関数を用意したいと思っています。
> 
> ※ イメージ
> mb_strwidth_EastAsian
> mb_strimwidth_EastAsian
> 
> この場合、既存のmbstring.*設定値を意識せずに両方の実装を常に利用
> する事ができます。
> 
> 
> 引数を追加する事も考えましたが、mbstringでは一番最後の引数は文字
> エンコーディングを指定する事が慣例となっているので、間に追加する事は
> 明示的に文字エンコーディングを指定している人達にとっては迷惑な話かも
> しれないので考えるのを止めました。
> 
> # 慣例に従わない(一番最後に追加する)のも一つの案ではありますが。
> 
> 
> 他には
> 
> ・mbstring.languageの設定値を元に動的に変化さる判定に用いる
> ・新たにini定義(mbstring.eaw_ambiguous_width?)を追加
> 
> 等、何かしらの情報を用いて動的に判定を変化させる話もありました。
> 他に妙案があれば教えてください。
> 
> 
> 個人的には、iniの定義を元に動的に変化さる判定させる方法は不幸な気が
> してきました(常にどちから固定になるので使い方に余計に気を使う)。
> 



PHP-dev メーリングリストの案内