[c++]文字列から1文字ずつ、サロゲートペアと異体字セレクタを考慮しつつ取り出す

Unicode文字列から「1文字ずつ」を取り出す場合、異体字セレクタやサロゲートペアを考慮に入れる必要がある。
以下、日本語環境での話(モンゴル文字の異体字セレクタ等は未対応)。

1.std::wstring(2バイト環境)の場合

以前の記事で書いたとおり、異体字セレクタは文字の末尾に4byte分の情報が付加される。(C++Builderで異体字セレクタを使う)

このため、wchar_tが2byteの場合、wstringの「1文字」が取り得るバイト数は以下の4通りになる。

「1文字」の4パターン バイト数
基本文字(基本多言語面) 2byte
サロゲートペア 4byte
基本文字の異体字セレクタ付 6byte
サロゲートペアの異体字セレクタ付 8byte

以下、文字列から、1文字ずつをstd::vectorに格納するメソッド。

(wchar_tが16bit/unsignedであることを前提にしたコード)

2.std::string(utf-8)の場合

utf-8文字列をstd::stringに突っ込んで使う場合もあるので(自分自身はやらないが)、ちょっと調べた。 以下のような方法で取り出せる(たぶん)。

utf-8文字は、先頭バイトを見ると1文字分のバイト数が分かるので、サロゲートペア判定は特に必要ない。 だが、そこで取れるバイト数には異体字セレクタ情報は反映されないので、自前で処理する必要がある。

文字に付加される異体字セレクタは計4byte分で、漢字用のIVSでは以下の範囲を取るようだ。

バイト順 値の範囲
0xF3 (固定)
0xA0 (固定)
0x84~0x87
0x80~0xBF
(0x80~0xAF)

4バイト目は、3バイト目が0x87の時に限り最大で0xAF(たぶん)。

以下、utf-8文字列の格納されたstd::stringから1文字ずつ取り出すメソッド。
なお、IVS最初の0xF3は、単体でも1byteで文字を表現するので、まず2byte分を判定する必要がある。

3.サンプルプログラム

C++Builderによるサンプルプログラム。

TFormにTEditとTMemoをひとつずつ、TButtonを2つ貼り付ける。
TEditとTMemoのフォントは游明朝、サイズを40にしておく。 TEditに以下の文字列を入れておく。

文字列の漢字部分は、最初の「辻」から順に
・基本文字
・異体字セレクタ
・基本文字
・サロゲートペア
・サロゲートペア
・サロゲートペアの異体字セレクタ付
英字は小文字のaが全角、大文字のAは半角。

3-1.std::wstringを使った例

Button1Clickを次のように実装。

結果:

3-2.std::stringを使った例

C++BuilderにはUTF8String型があるので、変換にはそれを使っている。
Button2Clickを以下のように実装。

結果:
 
以上、とりあえずのテストでは上手くいっているが、何か見落とし等があるかも。

続・C++Builderで異体字セレクタを使う サロゲートペアの場合

サロゲートペア文字にも異体字セレクタ対象の文字があることに、「C++Builderで異体字セレクタを使う」の投稿後に気付いた。

1.表示は簡単

基本は変わらない。対象文字のコードポイントの上位・下位バイトが分かっていれば、以下の数行のコードで表示できる。
なおTEditのフォントは、Windows8.1以降に標準バンドルされている游明朝を使っている。(オープンソースの花園明朝Aを使えば、より多くの異体字が表示できる)

表示結果

標準字形と異体字を並べてみる。

表示結果

2.両対応の汎用メソッド

実際にはコードポイントをいちいち探したり求めるのは面倒くさいので、実際の文字と異体字番号(ゼロから指定)から異体字を求める汎用メソッドを作ってみる。サロゲートかどうかも気にしなくてよい。
(以下のサンプルは、Stringが複数の文字からなる場合は、先頭文字の異体字を返す)

テストコード。
LabeledEditからIVS番号を取得して、文字を変換する。
2つのTEditのフォントは游明朝に設定している。

実行結果。

3.TRichEditの挙動が非サロゲートと異なる(?)

サロゲートペアの異体字をTRichEditに表示した場合、非サロゲートペアの場合と異なり、余分な空白が表示されないようだ。
異体字と標準字形を並べて表示してみると…。(左がTMemo、右がTRichEdit)

この挙動は謎。
なお、異体字の後ろにカーソルを置いた状態で、BackSpaceキーを押下すると〝先祖返り〟する挙動は、非サロゲート文字と変わらない。

C++Builder10.2.3とWindows10で確認。

C++Builderで異体字セレクタを使う

0.異体字セレクタとは

非常に大雑把かつ乱暴に言うと、山ほどある異体字を表示させるための仕組みのひとつ。
文字にいちいち固有の番号を割り当てるのではなく、番号に枝番号(符号)を付ける仕掛けである。
対応するフォントが必要だが、Windowsに標準添付の一部フォントでもそれなりに対応している。
詳しくはWikipediaなど参照してもらった方がいい。

Windows7からメモ帳でも利用可能になったので試してみた人も多いかもしれない。

1.表示は簡単

とりあえずよくある「1点しんにょうの辻」をTEditに表示する。
フォームにTEditとTButtonを一つずつ貼り付け、TEditのフォントはMS 明朝、フォントサイズは判別しやすいように30にしておく。
以下の数行のコードで、1点しんにょうの辻が表示される。

このように表示される。

2.仕組みとか

異体字セレクタは、通常の文字番号に、0xE0100から始まる枝番号を付けることで作動する。(最大で240までの枝番号が付けられるが、実際にそんなにたくさんの枝番号が定義された文字は現在ない。)
0xE0100は2byteで収まらないので、当然、wchar_tに格納できない。(C++Builderはwchar_tが2byte)
が、上位2byteは0xDB40で固定。下位2byteに0xDD00~0xDDEFまでのいずれかを格納してやればよい。

なお存在しない枝番を指定した場合、単に標準の文字が表示される。

3.フォントによって異なる枝番号

どの枝番号にどの異体字形が割り振られているかは、実はフォントによって違う。(正確に言うとフォントによって採用している字形コレクションが違う)。
異なるフォントを設定した3つのMemoに、「辻」の異体字の枝番を順に表示してみる。

実行結果

1点しんにょうの辻は、IPAmj明朝の場合は3番目、游明朝およびMS明朝なら1番目に割り振られているのが分かる。

いったん表示した後、フォントを変えると、文字が変化してしまう危険がある。

4.1文字で3文字分

文字数を数えるプログラムなどでは注意が必要。
SystemのLength()でもstd::wstringのlength()でも、3文字になる。
サロゲートペアの判定はIsSurrogateで簡単だが、異体字セレクタの場合はもう一工夫必要になるはず。

5.TMemoとTRichEditで挙動が異なる

異体字セレクタ文字は、TMemoとTRichEditで挙動・表示が違う。
1点しんにょうの辻と通常の辻の2文字をそれぞれ表示してみる。

実行結果。左がTMemo, 右がTRichEdit。

このようにRichEditは枝番分の文字の空きが取られてしまう。

ちなみに、この空きをキーボードで削除すると

枝番情報が消えるので最初の文字が通常の「辻」に戻ってしまう。

また、Memoの方でも、1点しんにょうの辻の後ろにカーソルを置きBackSpaceキーを押すと、文字は消えないがやはり先祖返りする。



以上、基本的にVclとFireMonkey共通。(ただしFireMonkeyにはRichEditはない。)
C++Builder10.2.3 / Windows10 で確認。

Unicodeのコード番号からStringを取得する

VclとFireMonkey共通。

“80CC”等、コードポイント(番号)の文字列をStringに変換するには、基本的には以下でOK。

ただし実際にはサロゲートペア用に割り当てられた番号(D800~DFFF)に配慮する必要がある。Androidで、サロゲートペア用の番号を指定するとアプリが落ちる場合もある(Android4.4.2で確認)
そこに配慮した関数。

サロゲートペア文字も取得するには、もう一手間が必要。
サロゲートペア用の番号は”20B9F”など5桁になる。
少し汎用にstd::wstringを使った関数。(注:C++Builderはwchar_tが2byte)

(注:C++Builder Berlin Update2では、Androidではwstring版の”wcstol”がバグっているようなので、さらに一手間かけてstring版の”strtol”を使う必要がある)

念のため、Stringに変換するには以下のように呼び出す。

(なおC++Builder BerlinではStringはUnicodeStringのtypedef)

C++Builder Berlin Update2 / Windows7(Sp1)で確認。