読者です 読者をやめる 読者になる 読者になる

mixi engineer blog

ミクシィ・グループで、実際に開発に携わっているエンジニア達が執筆している公式ブログです。様々なサービスの開発や運用を行っていく際に得た技術情報から採用情報まで、有益な情報を幅広く取り扱っています。

絵文字だョ! 符号化文字集合(前編)

iOS Objective-C

先日取り上げて頂いたテック総研のインタビューでは残念ながら時間の都合で、ろくろを回す事が出来なかった、iPhoneアプリ開発担当の七尾です。いやー残念。
先日お伝えしたAppleカラー絵文字文字コード表にUnicodeコードポイント、UTF-8、SoftBankUnicodeも追加したので、お知らせします。

iOS Emoji - GitHub Pages

ついでに各種変換処理なども書いたり、Unicodeの仕様を調べたりしたので、文字コードのおさらいとして、いくつかUnicode絵文字を扱う上での注意点についても、メモしておこうと思います。と思ったら結構な分量になってしまったので、前半と後半に分けてお送りします。

UnicodeSet

そもそも絵文字ってどこからどこまで?という問いに答えてくれるのが、UnicodeSetです。 よく使われるものだと\p{Han}(漢字の範囲を示す)だとか\p{Hiragana}(ひらがなの範囲を示す)などはよく見かけると思いますが、Unicode6.0からは、\p{Emoji}も定義されたようです。 おそらくまだ\p{Emoji}を利用できる環境は少ないとは思いますが、Unicode Utilities: UnicodeSetが利用できるので結構便利です。入力フォームに「\p{Emoji}」と入力し、「show set」ボタンを押すと絵文字の範囲一覧が表示されます。「Escape」にチェックを入れると、Unicodeコードポイントでの表記になるので、サーバ側で絵文字をマッチさせたい場合の参考にもなります。

また、利用できるUnicodeSetの一覧とその他諸々の使い方はこちらから確認できます。
list-unicodeset - Manipulate sets of Unicode characters

Surrogate Pair(サロゲートペア)/代用対

NSStringを扱う上で知らなければいけないのが、NSStringが内部的にはUnicodeのUTF-16エンコーディングな文字列であるという点です。
そしてUTF-16で特に絵文字などを利用する際に注意しなければいけないのが、サロゲートペアです。
UTF-16では一般的な文字/記号が収録されたBMP(基本多言語面)以外にはサロゲートペアが使われています。

UTF-16が考案された当初は16bitで全ての文字を表すことができると思われていました。
しかし、各国の文字追加要求が相次ぎ、やはり足りないという事で、未定義領域1024文字分を2つ組み合わせて一文字を表現しようという手法がサロゲートペアです。

サロゲートペアを利用すれば1024個の要素を2つの組合せで表現するので、単純に計算して、
1024 ^ 2 = 1,048,576文字
をカバーする事が出来ます。

具体的には、以下のようなものが典型的なサロゲートペアです。

上からUnicodeコードポイント(符号位置)、UTF-8、UTF-16となっています。
またこの際、前半の要素(High Surrogates)には0xD800〜0xDBFFの範囲が、後半の要素(Low Surrogates)には0xDC00〜0xDFFFの範囲が使われる事になっています。

2文字の組み合わせを利用することで定義可能な文字数の問題は解決しますが、UTF-16な文字を扱うシステムの実装でその文字数を数えるとなると話は別です。
NSStringのlengthメソッドではHigh SurrogatesもLow Surrogatesも1文字としてカウントします。
つまり、サロゲートペアを1文字としてカウントしたい場合は自前でサロゲートペアを判断する必要があります。

具体的には1文字づつ文字列を取り出して値を評価しなければいけません。
もしくは代用対に使われる領域は決まっているので、文字数のカウントという限定的な用途に限って言うと、High Surrogatesのみ予め削除してしまうという作戦もアリかなとは思います。

文字列に対して変更を行わないという方針の場合は、どちらかを、0文字としてカウントするか、High Surrogatesの後にLow Surrogatesが来た事を確認して、1文字としてカウントするのか?その場合例外をどうするのか?という点に関しては、要件によって重要度が分かれるところだと思いますので、ここでは結論付けないでおきたいと思います:)

unichar c = [emoji characterAtIndex:i];
if (0xD800 <= c && c <= 0xDBFF) {
    // High Surrogates なので、文字数カウントしない?
}
else if (0xDC00 <= c && c <= 0xDFFF) {
    // Low Surrogates なので、文字数カウントしない?
}
else {
    ++count;
}

というわけで前半はここまでとさせて頂きます。次回は結合文字とその向こう側(?)について触れたいと思います。それではまた次回