絵文字を含むNSStringの正確な文字数をカウントする(2)
なるほど、確かにカウントできない文字がある。いったいどんな規則性があるのかと思っていたら、絵文字の文字コードをまとめていたサイトを教えてもらった。
それがこれ↓
iOS Emoji
う~ん、まるで規則性が見当たらないと思っていたらピンときた。前は UTF8String
とかやってたけど、内部の文字コードはもしかしてUTF-16ではなかろうか?と思って調べたらやっぱりUTF-16だった。それならわざわざUTF-8に変換しないでUTF-16のまま処理した方が良い。
UTF-16といえば主要なトピックはサロゲートペアだ。0xD800-0xDBFFが上位サロゲート、0xDC00-0xDFFFが下位サロゲート。( Wikipedia#Unicode 参考)なので上位サロゲートを検出したら1文字スキップする。(1)
さらに、国旗の絵文字には Regional Indicator という専用のシンボルが2つの組み合わせで使われている。( Unicode.orgのこのページ の最下段に記載されている。)なのでRegional Indicatorを検出して次のサロゲートペアもRegional Indicatorだったら3文字スキップする。(2)
あと、ガラケーではお馴染みの四角で囲まれた数字には通常の数字の後に COMBINING ENCLOSING KEYCAP という専用の文字が配置されている。だがこれは単純に無視すればいいだろう。(3)
そうすると、こんな感じで書けばいいかな?ちゃんと実装するには文字のインデックスが out of bounds にならんようにした方がいいかも。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | - (NSUInteger) actualNSStringSize:(NSString *) txt { int len = [txt length]; NSUInteger count = 0; for (NSUInteger i = 0; i < len; i++) { unichar c = [txt characterAtIndex:i]; if (0xD83C == c) { unichar c1 = [txt characterAtIndex:i+1]; if ((0xDDE6 <= c1) && (c1 <= 0xDDFF)) { unichar c2 = [txt characterAtIndex:i+2]; if (0xD83C == c2) { unichar c3 = [txt characterAtIndex:i+3]; if ((0xDDE6 <= c3) && (c3 <= 0xDDFF)) { // 国旗なのでスキップ・・・(2) i += 3; ++count; continue; } } } i++; ++count; } else if (0xD800 <= c && c <= 0xDBFF) { // 上位サロゲート・・・(1) i++; ++count; } else if (0xDC00 <= c && c <= 0xDFFF) { // 下位サロゲートなので念のため無視 } else if (0x20E3 == c) { // 囲みなので無視・・・(3) } else { ++count; } } return count; } |
この記事、大変役立ちました。ありがとうございます。
こちらこそありがとうございます。
あなたの記事は有用で参考になったが少し改良が出てきたように見受けられました。具体的には無視すべきコードに0xFE0Fと0xFE0E を加える必要があると見ています。0xFE0Fは0xFE0Eは直前に出てきたutf-8文字をそれぞれEmoji見た目かテキスト見た目に切り替えるかの指定のために利用されている。これらは装飾的で実際には見えないので文字数としては無視すべきかと考えています。
上記内容があなたの記事の助けになると嬉しいです。ありがとう
ご指摘ありがとうございます。確かに異体字セレクタについては考慮されていません。0xFE00 ~ 0xFE0Fを除外すると良さそうですね。Unicodeは次々拡張さるので都度見直すことの必要性を認識しました。ありがとうございます。