tmytのらくがき

個人の日記レベルです

最近のWindows 10で絵文字を書記素クラスタ単位で数える

UWPからP/Invokeすることが許可されているAPIが列挙されているページがあります。今日も特に理由もなく眺めていたらicuuc.dllが16299から使えるようになっていたことに今更気づきました。

docs.microsoft.com

icuuc.dllはICUの一部で、ICUはInternational Components for Unicodeの略で、Unicodeの難しいいろいろを面倒見てくれるライブラリです。 今日はこれを使って絵文字を書記素クラスタ単位で数えてみたいと思います。

今回のコードはこのページを参考にしました。C++からC#に書き直しただけでほぼそのままです。

qiita.com

書記素クラスタ

詳しいことはここを読むといいと思います。

ufcpp.net

Unicodeを1文字ずつ分割するときに使うアルゴリズムです。いろいろ調べても基本的にテーブル引きしないといけないようなことが書いてました。

.NETから呼ぶ

.NETから呼ぶにはP/InvokeすればOKです。今回は、書記素クラスタに分割するAPI周りだけ定義を作りました。

gist.github.com

これを使って、こんなコードを書くと書記素クラスタに基づいて、何文字で構成された文字列なのかがわかります。 参考にしたコードでは、utext_openUTF8を使っていたけれども、.NETのStringはUTF-16なのでutext_openUCharsを使ったほうが都合がいいです。

public static int TextLengthInGrapheme(string s)
{
    var iterator = Icuuc.ubrk_open(UBreakIteratorType.UBRK_CHARACTER, Icuuc.uloc_getDefault(), null, 0, out _);
    var utext = Icuuc.utext_openUChars(IntPtr.Zero, s, s.Length, out _);
    Icuuc.ubrk_setUText(iterator, utext, out _);
    var n = 0;
    while (Icuuc.ubrk_next(iterator) != Icuuc.UBRK_DONE) n++;
    Icuuc.ubrk_close(iterator);
    Icuuc.utext_close(utext);
    return n;
}

また、文字を切り出すにはこんな感じで呼べばよさそうです。

var iterator = Icuuc.ubrk_open(UBreakIteratorType.UBRK_CHARACTER, Icuuc.uloc_getDefault(), null, 0, out _);
var utext = Icuuc.utext_openUChars(IntPtr.Zero, Text, Text.Length, out _);
Icuuc.ubrk_setUText(iterator, utext, out _);
int current = Icuuc.ubrk_current(iterator);
while (current != Icuuc.UBRK_DONE)
{
    int next = Icuuc.ubrk_next(iterator);
    if (next == Icuuc.UBRK_DONE) break;
    int size = next - current;
    Debug.WriteLine($"{current}-{next}: {Text.Substring(current, size)}");
    current = next;
}
Icuuc.ubrk_close(iterator);
Icuuc.utext_close(utext);

比べてみる

今回作ったコードと、.NETのStringInfoクラスと、String[Index]でえられる結果を並べてみました。 左から、ICU、StringInfo、String[Index]です。

f:id:tmyt:20190420205012p:plain

ICUは1文字としてカウントされてます。StringInfoはサロゲートペアを認識はしてますが、書記素分割には対応してないので11文字、String[Index]はサロゲートペアをバラバラにカウントするので19文字に見えています。 StringInfoを使っていると👩🏻‍👩🏿‍👧🏼‍👧🏾が11文字としてカウントされていたのが正しく1文字になるのでTwitterクライアントの文字カウントがより正確になります*1

おまけ

f:id:tmyt:20190420205540p:plain

Twitter…お前…!!!!

*1:Aristeaは近日対応したのがリリースされます