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

Seiji Masugata s.masugata @ digicom.dnp.co.jp
2007年 8月 9日 (木) 13:54:44 JST


こんにちわ、桝形です。

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の定義を元に動的に変化さる判定させる方法は不幸な気が
してきました(常にどちから固定になるので使い方に余計に気を使う)。

-- 
Seiji Masugata <s.masugata @ digicom.dnp.co.jp>



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