UTF-8の文字列の文字数を数える

## UTF-8をコードポイントに分解する

まずはじめに、コマンドライン引数で入力された文字列を、コードポイントごとに表示するプログラムを作成します。
UTF-8からコードポイントを求める方法は、[Wikipedia](http://ja.wikipedia.org/wiki/UTF-8) で説明されているとおりです。

**注意:** 以下の説明は、Windowsでは動作しません。Linux, FreeBSD, MacOSX (または[Cygwin](https://www.cygwin.com/))で試してください。

```c
#include <stdio.h>

/**
 * next_codepoint(&ptr);
 * ptrを、次のコードポイントの位置に動かす
 */
static int next_codepoint(const char **pp)
{
  const char *p = *pp;
  int c = -1;

  if (*p == 0) {
  } else if ((*p & 0x80) == 0) {
    c = *p++ & 0xFF;
  } else if ((*p & 0xE0) == 0xC0) {
    c = *p++ & 0x1F;
    c = (c << 6) | (*p++ & 0x3F);
  } else if ((*p & 0xF0) == 0xE0) {
    c = *p++ & 0x0F;
    c = (c << 6) | (*p++ & 0x3F);
    c = (c << 6) | (*p++ & 0x3F);
  } else if ((*p & 0xF8) == 0xF0) {
    c = *p++ & 0x07;
    c = (c << 6) | (*p++ & 0x3F);
    c = (c << 6) | (*p++ & 0x3F);
    c = (c << 6) | (*p++ & 0x3F);
  }
  *pp = p;
  return c;
}
int main(int argc, char **argv)
{
  if (argc >= 2) {
    const char *p = argv[1];
    int ch;
    int count = 0;

    while ((ch = next_codepoint(&p)) != -1) {
      printf("U+%04X ", ch);
      count++;
    }
    printf("\nCount = %d\n", count);
  }
  return 0;
}
```

実行結果は以下のとおりです。「あいうえお」はコードポイント5つから構成されています。

```
$ ./a.out あいうえお
U+3042 U+3044 U+3046 U+3048 U+304A 
Count = 5
```

## グラハム書記素について
例えば、次の文字列の文字数を数えてみましょう。
`ポイント`
そのままコピー&ペーストして、実行してみると、4文字と判定されます。

```
$ ./a.out ポイント
U+30DD U+30A4 U+30F3 U+30C8 
Count = 4
```

では、次の文字列ではどうなるでしょうか。
`ポイント`
そのままコピー&ペーストして、実行してみると、5文字と判定されます。

```
$ ./a.out ポイント
U+30DB U+309A U+30A4 U+30F3 U+30C8 
Count = 5
```

後者は、「ポ」が正規分解済みなので、コードポイント数は2になりますが、文字数は1になります。
以下の説明では、Unicodeで単一のコードが与えられている文字等を「コードポイント」、一般的に1文字と認識する単位(厳密に言うとグラハム書記素)を「書記素」と表します。

### Unicode.orgのデータを利用する
書記素を数えるには、テーブルを参照して書記素の区切り位置を探すしかありません。
現在、最新版のUnicode 7.0.0のGraphemeBreakPropertyが利用できるので、これを利用します。

ftp://ftp.unicode.org/Public/7.0.0/ucd/auxiliary/GraphemeBreakProperty.txt

このテキストファイルは、以下のように書かれています。
(#以降はコメントです)

```
000A          ; LF # Cc       <control-000A>
```

U+000Aは、LFに分類されるという意味です。

```
3099..309A    ; Extend # Mn   [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
```

U+3099からU+309Aは、Extendに分類されるという意味です。
これらの範囲に含まれないコードポイントは、Otherに分類されます。

以下のサンプルコードを動かすには、GraphemeBreakProperty.txtからgrapheme.datというバイナリファイルを作成する必要がありますので、頑張って作ってください。
grapheme.datは、例えばU+309AがExtendの場合、先頭から0x309Aバイト目が4になります。

```c
#include <stdio.h>
#include <stdlib.h>

enum {
  CR = 1,
  LF = 2,
  Control = 3,
  Extend = 4,
  SpacingMark = 5,
  L = 6,
  V = 7,
  T = 8,
  LV = 9,
  LVT = 10,
  Regional_Indicator = 11,
};
enum {
  MAX_CODEPOINT = 0x10000,
};

/**
 * next_codepoint(&ptr);
 * ptrを、次のコードポイントの位置に動かす
 */
static int next_codepoint(const char **pp)
{
  const char *p = *pp;
  int c = -1;

  if (*p == 0) {
  } else if ((*p & 0x80) == 0) {
    c = *p++ & 0xFF;
  } else if ((*p & 0xE0) == 0xC0) {
    c = *p++ & 0x1F;
    c = (c << 6) | (*p++ & 0x3F);
  } else if ((*p & 0xF0) == 0xE0) {
    c = *p++ & 0x0F;
    c = (c << 6) | (*p++ & 0x3F);
    c = (c << 6) | (*p++ & 0x3F);
  } else if ((*p & 0xF8) == 0xF0) {
    c = *p++ & 0x07;
    c = (c << 6) | (*p++ & 0x3F);
    c = (c << 6) | (*p++ & 0x3F);
    c = (c << 6) | (*p++ & 0x3F);
  }
  *pp = p;
  return c;
}
char *load_grapheme_data(void)
{
  FILE *fp = fopen("grapheme.dat", "rb");
  char *dat = malloc(MAX_CODEPOINT);
  fread(dat, 1, MAX_CODEPOINT, fp);
  fclose(fp);
  return dat;
}
int main(int argc, char **argv)
{
  if (argc >= 2) {
    const char *p = argv[1];
    int ch;
    char *dat = load_grapheme_data();

    while ((ch = next_codepoint(&p)) != -1) {
      if (ch < MAX_CODEPOINT) {
        switch (dat[ch]) {
        case CR:
          printf("CR ");
          break;
        case LF:
          printf("LF ");
          break;
        case Control:
          printf("Control ");
          break;
        case Extend:
          printf("Extend ");
          break;
        case SpacingMark:
          printf("SpacingMark ");
          break;
        case L:
          printf("L ");
          break;
        case V:
          printf("V ");
          break;
        case T:
          printf("T ");
          break;
        case LV:
          printf("LV ");
          break;
        case LVT:
          printf("LVT ");
          break;
        default:
          printf("Other ");
          break;
        }
      }
    }
    printf("\n");
  }
  return 0;
}
```

実行結果は、以下のようになります。

```
$ ./a.out ポイント
Other Extend Other Other Other 
```

### GraphemeBreakPropertyから区切り位置を探す
ここまで来たらもう一息です。
[Grapheme Break Chart](http://unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakTest.html) の表を見ると、区切り位置がどこかがわかります。
以下の表は、一部を抜粋したものです。

![Screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/48925/25f100a4-db47-ec08-7eac-fa5e4a572441.png)

表の縦の列が前のコードポイント、横の列が後ろのコードポイントを表しています。「÷」が書記素の区切り、「×」が書記素の区切りではないことを表しています。
例えば、前のコードポイントがExtend、後ろのコードポイントがOtherなら、「÷」となるので書記素が区切られますが、前のコードポイントがOther、後ろのコードポイントがExtendなら、「×」となるので書記素が区切られません。(前のコードポイントと合わせて一つの書記素を作る)

```c
/*
 * main関数以外は、変更していないので省略
 */
int main(int argc, char **argv)
{
  /**
   * http://unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakTest.html
   */
  static const unsigned short grapheme_break[] = {
    0x0030,
    0x0004,
    0x0000,
    0x0000,
    0x0030,
    0x0030,
    0x06f0,
    0x01b0,
    0x0130,
    0x01b0,
    0x0130,
    0x0830,
  };
  if (argc >= 2) {
    const char *p = argv[1];
    int ch, type, prev_type = -1;
    char *dat = load_grapheme_data();
    int count = 0;

    while ((ch = next_codepoint(&p)) != -1) {
      if (ch < MAX_CODEPOINT) {
        type = dat[ch];

        if (prev_type >= 0) {
          if ((grapheme_break[prev_type] & (1 << type)) == 0) {
            printf("| ");
            count++;
          }
        }
        prev_type = type;

        switch (type) {
        case CR:
          printf("CR ");
          break;
        case LF:
          printf("LF ");
          break;
        case Control:
          printf("Control ");
          break;
        case Extend:
          printf("Extend ");
          break;
        case SpacingMark:
          printf("SpacingMark ");
          break;
        case L:
          printf("L ");
          break;
        case V:
          printf("V ");
          break;
        case T:
          printf("T ");
          break;
        case LV:
          printf("LV ");
          break;
        case LVT:
          printf("LVT ");
          break;
        default:
          printf("Other ");
          break;
        }
      }
    }
    if (prev_type >= 0) {
      count++;
    }
    printf("\nCount = %d\n", count);
  }
  return 0;
}
```

実行結果は、以下のようになります。書記素ごとに | で区切られます。

```
$ ./a.out ポイント
Other Extend | Other | Other | Other 
Count = 4
```

## 最後に
自前で作るより、[ICU](http://site.icu-project.org/)とかを使いましょう。