ｺ゙ｺﾞｺ゛ゴゴコﾞコ゛をｺﾞｺﾞｺﾞゴゴゴゴにするGo言語のライブラリを作ってみた

# はじめに

Unicode 正規化って日本語の処理に向いていないというか、ちょっと使いどころが難しいと思いませんか？

ということで、Golang のノーマライゼーション・ライブラリを作ってみました。使えるレベルに仕上がったと思うので **GaGa** という名前で公開します。

Unicode 正規化に関しては、既に Golang の準標準ライブラリに、

* golang.org/x/text/unicode/norm
* golang.org/x/text/width

というパッケージがありますが、今回作ったものと機能を比較すると以下のような違いがあります。

|機能など|gaga|norm|width|補足|
|:-:|:-:|:-:|:-:|:--|
|全角-半角<br>変換|〇|△|△|詳細は後述しますが、norm と width は期待外れな結果を返します。|
|ひらがな-<br>カタカナ<br>変換|〇|×|×|「ひらがな-カタカナ変換器」はノーマライザというよりはトランスレータの類かもしれませんね。|
|大文字-<br>小文字<br>変換|〇|×|×|これは Golang の標準ライブラリ strings パッケージでサポートされていますね。これもトランスレータの類でしょうか。|
|濁点・半濁点<br>の分解合成|〇|△|△|これも norm と width は期待外れな振舞いをします。詳細は後述します。|
|文字種別ごとの変換|〇|×|×|gaga は アルファベット、ラテン数字、ラテン記号、ひらがな、カタカナ、かな記号ごとに変換方法を指定できます。|
|パフォーマンス|〇|△|◎|処理時間を比較するとこんな感じです。<br>gaga : norm : width ≒ 2 : 6 : 1|
|サポートする文字|△|◎|◎|gaga のサポート範囲はラテン文字とひらがなカタカナを中心とする462文字です。|

ちなみに、Golang の準標準ライブラリの振舞いが期待外れなのは、開発チームや実装に問題があるということではありません。世界中のありとあらゆる文字を取り扱わなければならない Unicode の標準化において、多種多様な文化間のバランスを取りながら最大公約数的に策定せざるを得なかった正規化フォームの仕様が、「かな文字」をネイティブとして扱う我々にとっては完全なものではないということなのだと思います。

# 目次

* [Unicode の 7 種類の「ゴ」について](#unicode-の-7-種類のゴについて)
* [Combining characters について](#combining-characters-について)
    * [Combining character のメリット](#combining-character-のメリット)
    * [Combining character のデメリット](#combining-character-のデメリット)
* [Unicode 正規化フォームの問題点](#unicode-正規化フォームの問題点)
    * [正準等価性](#正準等価性)
    * [互換等価性](#互換等価性)
    * [Golang の準標準ライブラリ](#golang-の準標準ライブラリ)
        * [golang.org/x/text/width の問題点](#golangorgxtextwidth-の問題点)
        * [golang.org/x/text/unicode/norm の問題点](#golangorgxtextunicodenorm-の問題点)
* [どんなライブラリを作ったのか？](#どんなライブラリを作ったのか)
    * [ダウンロード](#ダウンロード)
    * [使い方](#使い方)
    * [エラーハンドリングについて](#エラーハンドリングについて)
    * [コマンドラインツール](#コマンドラインツール)
    * [フラグの種類](#フラグの種類)
* [どのように実装したのか？](#どのように実装したのか)
* [パフォーマンスは良いのか？](#パフォーマンスは良いのか)
* [GaGa がサポートする 462文字](#gaga-がサポートする-462文字)

# Unicode の 7 種類の「ゴ」について

Unicode でカタカナの「ゴ」と書くとき、多くの人はコードポイント U+30B4（名称: KATAKANA LETTER GO）を使うと思います。

ところが歴史的な事情により UNICODE には 3 種類の「濁点マーク」が登録されていて、

**3 種類の濁点マーク**

|グリフ|Unicode|JIS|名前|
|:-:|:-:|:-:|:--|
|゛|U+309B|212B<BR>(JIS X 0208)|KATAKANA-HIRAGANA VOICED SOUND MARK|
|ﾞ|U+FF9E|DE<BR>(JIS X 0201)|HALFWIDTH KATAKANA VOICED SOUND MARK|
|◌゙|U+3099|(undefined)|COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK|

これに濁点なしの全角の「コ」と半角の「ｺ」を組み合わせると、「ゴ」という 1 つの文字を 7 通りの方法で表現できてしまいます。

**7 通りの「ゴ」**

|No.|グリフ|構成|コードポイント|説明|
|:-:|:--|:--|:--|:--|
|1|ゴ|<font color="gray">[ </font>ゴ<font color="gray"> ]</font>|<font color="gray">[ </font>U+30B4<font color="gray"> ]</font>|<font color="gray">[ </font>濁点付き 全角文字<font color="gray"> ]</font>|
|2|コ゛|<font color="gray">[ </font>コ<font color="gray"> ]</font> +<br><font color="gray">[ </font>゛<font color="gray"> ]</font>|<font color="gray">[ </font>U+30B3<font color="gray"> ]</font> + <br><font color="gray">[ </font>U+309B<font color="gray"> ]</font>|<font color="gray">[ </font>濁点なし 全角文字<font color="gray"> ]</font> +<br><font color="gray">[ </font>全角 濁点<font color="gray"> ]</font>|
|3|コﾞ|<font color="gray">[ </font>コ<font color="gray"> ]</font> +<br><font color="gray">[ </font>ﾞ<font color="gray"> ]</font>|<font color="gray">[ </font>U+30B3<font color="gray"> ]</font> + <br><font color="gray">[ </font>U+FF9E<font color="gray"> ]</font>|<font color="gray">[ </font>濁点なし 全角文字<font color="gray"> ]</font> +<br><font color="gray">[ </font>半角 濁点<font color="gray"> ]</font>|
|4|ゴ|<font color="gray">[ </font>コ<font color="gray"> ]</font> +<br><font color="gray">[ </font> ゙<font color="gray"> ]</font>|<font color="gray">[ </font>U+30B3<font color="gray"> ]</font> + <br><font color="gray">[ </font>U+3099<font color="gray"> ]</font>|<font color="gray">[ </font>濁点なし 全角文字<font color="gray"> ]</font> +<br><font color="gray">[ </font>幅なし 濁点<font color="gray"> ]</font>|
|5|ｺ゛|<font color="gray">[ </font>ｺ<font color="gray"> ]</font> +<br><font color="gray">[ </font>゛<font color="gray"> ]</font>|<font color="gray">[ </font>U+FF7A<font color="gray"> ]</font> + <br><font color="gray">[ </font>U+309B<font color="gray"> ]</font>|<font color="gray">[ </font>濁点なし 半角文字<font color="gray"> ]</font> +<br><font color="gray">[ </font>全角 濁点<font color="gray"> ]</font>|
|6|ｺﾞ|<font color="gray">[ </font>ｺ<font color="gray"> ]</font> +<br><font color="gray">[ </font>ﾞ<font color="gray"> ]</font>|<font color="gray">[ </font>U+FF7A<font color="gray"> ]</font> + <br><font color="gray">[ </font>U+FF9E<font color="gray"> ]</font>|<font color="gray">[ </font>濁点なし 半角文字<font color="gray"> ]</font> +<br><font color="gray">[ </font>半角 濁点<font color="gray"> ]</font>|
|7|ｺ゙|<font color="gray">[ </font>ｺ<font color="gray"> ]</font> +<br><font color="gray">[ </font> ゙<font color="gray"> ]</font>|<font color="gray">[ </font>U+FF7A<font color="gray"> ]</font> + <br><font color="gray">[ </font>U+3099<font color="gray"> ]</font>|<font color="gray">[ </font>濁点なし 半角文字<font color="gray"> ]</font> +<br><font color="gray">[ </font>幅なし 濁点<font color="gray"> ]</font>|

上表のグリフ欄の見え方は環境によって変わりますが、筆者の環境（Windows 10 + Chrome 85）では、上記 1 と 4、また 5 〜 7 は見た目上の違いがわかりません。

また、これは特にラテン文字に顕著ですが、プロポーショナルフォントが当たり前になった現代では、全角と半角の違いが昔より分かりにくくなりました。

そして、この「似ているのに異なる」ことが、

* 検索してても見つからない
* 統計を取る際のカテゴライズに苦労する

などといった、地味だけど面倒な問題を引き起こします。

この問題の対処方法はいくつかありますが、

|No.|アプローチ|ソリューションの具体例|補足|
|--:|:--|:--|:--|
|1|入力制限|Validation 検査|登録フォームなど情報の入口で入力できる文字を制限し、正規形のみを通過させるとてもポピュラーなアプローチ。|
|2|選択|プルダウンメニューなど|入力制限の一種。選択肢が少ないときは便利。|
|3|同値とみなす|DBMS の Collation|情報の出口での対処方法。データベースの文字セットに照合順序の属性を持たせ、比較の際に例えば半角と全角、大文字と小文字を同値とみなすアプローチ。|
|4|形を揃える|Unicode Normalization|正規化して形を整えてから比較するアプローチ。情報の入口でも適用できるが、Webサイトの登録フォームなどではあまり見かけない。|

本稿のテーマは上記 4 に関するものです。

誰かがついうっかり「ｺﾞｰ言語」や「コ゛ー言語」と入力してしまっても、由緒正しい「ゴー言語」ではないからといって、「その文字は入力できません！」などと拒絶するのではなく、そっと優しく正規の形に導いてあげませんか？ そのためのライブラリを作りました！という話です。

# Combining characters について

上述のとおり、Unicode には、JIS 由来の濁点マーク \[゛](U+309B)、\[ﾞ](U+FF9E) の他に、端末でレンダリングする際に基底文字に合成してレイアウトされる「幅ゼロの濁点マーク」\[◌゙](U+3099) が登録されていて、その名称には "COMBINING" というプレフィクスが付けられています。
(半濁点にも同様に \[ ゚ ](U+309A) があります)

普段の生活でこの Combining character (Non-space mark) を使う機会は少ないと思いますが、Unicode 正規化フォームの「正準等価性による分解と合成」においてもキーになる文字なので少しだけ触れておきます。

## Combining character のメリット

かつて JIS X 0208 には **ヴ** という文字はありましたが、**ゔ** はありませんでした。
JIS X 0213 では **ゔ** が登録され、他にも、**ヷ**（濁点付きの \[ワ]）、**カ゚**（半濁点付きの \[カ]）なども登録されました。

ところが、JIS X 0213 に **ヷ** はあるのに、**わ゙** はありません。
また、Unicode への **カ゚** の登録は見送られました。見送られた理由は Combining character の U+309A で合成できるから、ということのようです。

整理すると以下のようになります。

**特殊な濁点付き文字の例**

|グリフ<br>(Unicode)|代用表記<br>(JIS X 0208)|JIS X 0213<br>のサポート|Unicode<br>のサポート|補足|
|:-:|:-:|:-:|:-:|:--|
|ゔ|う゛|〇|〇||
|ヷ|ワ゛|〇|〇||
|わ゙|わ゛|×|×|グリフ欄:<br>\[わ]+\[U+3099]で表示|
|カ゚|カ゜|〇|×|グリフ欄:<br>\[カ]+\[U+309A]で表示|

また、以下のような文字も JIS に登録されていません。
（JIS に登録されていないので、当然 Unicode にも登録されていません）

|用途|グリフ<br>Unicode|代用表記<br>JIS X 0208|補足|
|:-:|:-:|:-:|:--|
|ジャイアントロボの決め台詞|マ゙|マ゛|グリフ欄:<br>\[マ]+\[U+3099]で表示|
|一部の官能表現など|ア゙|ア゛|グリフ欄:<br>\[ア]+\[U+3099]で表示|

近年、アーティスト名の表現方法が多彩になってきていますが、今後、濁音を持たない文字に濁点や半濁点を組み合わせた名前も出てくるかもしれません。

Combining character を使えば、新たにコードポイントを追加しなくても、このような要求を満たすことができます。

## Combining character のデメリット

現時点ではまだ普及しているとはいえないことが挙げられるでしょう。
環境によって見え方が変わってしまうのです。

同じプラットフォームで同じアプリケーションを使っていても、フォントにより見え方がまったく違います。
以下は筆者の環境で、ひとつの Excel シート内に、Combining Character を含む同一の文字列をフォントだけ変えて表示した例です。

<img width="500" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634588/474136ba-eabe-231b-31b2-c7b2d69446ae.png">

編集時の挙動もアプリケーションによって違います。
アプリケーションによって、濁点だけを削除することができたり、先行の文字にくっついて削除できなかったりと、違いがあります。
興味がある人は以下の文字列をエディタなどにコピペして試してみてください。

|え゚？゚ G゙A゙G゙A゙が゙？゙|
|:-:|

このように、Combining character はまだまだ普及しているとは言えません。現段階ではキャラクターベースのコミュニケーション用途には向きません。どうしても使いたいという場合は、環境要件を固定するか、特定の環境でラスタライズしたものを共有するなどの対応が必要になるでしょう。

# Unicode 正規化フォームの問題点

問題点について述べる前に Unicode 正規化フォーム (Unicode Normalization Form) について簡単に整理しておきます。

まず用語ですが、本稿では以下の用語を使って話を進めます。

|用語|別名<br>（かな文字の場合）|例|略称|
|:--|:--|:--|:--|:--|
|Voicing modifier|発声修飾子<br>（濁点または半濁点）|<font color="gray">\[</font> ゛ <font color="gray">]</font> <font color="gray">\[</font> ゜ <font color="gray">]</font>|VOM|
|Pre-composed character|合成済み文字<br>（濁点付き文字）|<font color="gray">[</font> ゴ <font color="gray">]</font>|Precomp|
|Base character|基底文字<br>（濁点なし文字）|<font color="gray">\[</font> コ <font color="gray">\]</font>|Base|
|Combining character|結合文字<br>（幅なしのVOM）<br>※全角と半角のVOMは含まない|<font color="gray">\[</font> ◌゙ <font color="gray"> <font color="gray">\]</font>|Combining|

次に Unicode の「正準等価性」と「互換等価性」について整理します。

## 正準等価性

まずは、正準等価性です。
正準等価性というのは、かな文字で言えば濁点付き文字（または半濁点付き文字）の等価性のことで、分解と合成により別の等価な文字シーケンスに置き換えることができます。

**正準等価性による分解・合成**

|正規化の種類|かな文字の挙動|例|
|:--|:--|:--|
|分解|Precomp を Base と Combining に分解する。|<font color="gray">[</font> ゴ <font color="gray">]</font> => <font color="gray">[</font> コ <font color="gray">]</font> + <font color="gray">[</font> ◌゙ <font color="gray">]</font>|
|合成|Base と Combining を Precomp に合成する。|<font color="gray">[</font> コ <font color="gray">]</font> + <font color="gray">[</font> ◌゙ <font color="gray">] => <font color="gray">[</font> ゴ <font color="gray">]</font></font>|

本稿のテーマからは逸れますが、ラテン文字のダイアクリティカルマークなども、正準等価性による分解と合成をすることで、これとよく似た変換が行われます。

## 互換等価性

次に、互換等価性です。
互換等価性は、かな文字で言えば全角と半角の等価性のことです。
分解により、かな文字は全角へ、ラテン文字は半角へ変換されます。

また、かな文字以外では「互換等価性による分解」で「文字幅」が変化するとは限らないので注意が必要です。
例えば、[㍉] という全角の非漢字文字は、 [ミ][リ] という全角カタカナ 2 文字に分解されます。

**互換等価性による分解**

|正規化の種類|かな文字の挙動|例|
|:--|:--|:--|
|分解|半角文字を全角文字にする。<br>（ラテン文字の場合は逆）|<font color="gray">[</font> ｺ <font color="gray">]</font> => <font color="gray">[</font> コ <font color="gray">]<br>[</font> Ａ <font color="gray">]</font> => <font color="gray">[</font> A <font color="gray">]</font></font>

なお、「互換等価性による合成」という言葉は Uicode 正規化の仕様には出てこないようですが、かな文字の全角から半角への変換はこれにあたると考えることもできるでしょう。

## Golang の準標準ライブラリ

冒頭で触れたとおり、Golang の準標準ライブラリには Unicode Normalization Form 関連のパッケージが 2 つありますが、どちらもかな文字の扱いに関して問題があり、期待した結果を返してくれません。

### golang.org/x/text/width の問題点

ひとつは golang.org/x/text/width パッケージで、これは互換等価性の分解と合成に近い働きをするものです。
（先に述べた通り互換等価性による合成という言葉は Unicode の仕様には出てきません)

要するに全角 (Full/Wide) と半角 (Half/Narrow) の変換をしてくれるものなのですが、このパッケージで 7 種類の「ゴ」を変換してみると可愛げのない挙動になります。

確認用のプログラムは以下のとおりです。

```golang:width_examples.go 
package main

import (
        "fmt"
        "golang.org/x/text/width"
        "strings"
)

func hexs(s string) string {
    var ss []string
    for _, r := range []rune(s) {
        e := fmt.Sprintf("%04X", r)
        ss = append(ss, e)
    }
    switch len(ss) {
    case 0:
        return "<empty>"
    case 1:
        return "[" + ss[0] + "]"
    default:
        return "[" + strings.Join(ss, " ") + "]"
    }
}

func main() {
        s := "ｺﾞｺ゛ｺ\u3099ゴコﾞコ゛コ\u3099"
        narrow := width.Narrow.String(s)
        widen := width.Widen.String(s)
        fold := width.Fold.String(s)

        fmt.Printf("SRC :\t%q,\n\t%s\n", s, hexs(s))
        fmt.Printf("NAR :\t%q,\n\t%s\n", narrow, hexs(narrow))
        fmt.Printf("WIDE:\t%q,\n\t%s\n", widen, hexs(widen))
        fmt.Printf("FOLD:\t%q,\n\t%s\n", fold, hexs(fold))
}
```

実行結果は以下のとおりです。

```bash:結果
$ go run width_examples.go 
SRC :   "ｺﾞｺ゛ｺ゙ゴコﾞコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B4 30B3 FF9E 30B3 309B 30B3 3099]
NAR :   "ｺﾞｺ゛ｺﾞゴｺﾞｺ゛ｺﾞ",
        [FF7A FF9E FF7A 309B FF7A FF9E 30B4 FF7A FF9E FF7A 309B FF7A FF9E]
WIDE:   "ゴコ゛ゴゴゴコ゛ゴ",
        [30B3 3099 30B3 309B 30B3 3099 30B4 30B3 3099 30B3 309B 30B3 3099]
FOLD:   "ゴコ゛ゴゴゴコ゛ゴ",
        [30B3 3099 30B3 309B 30B3 3099 30B4 30B3 3099 30B3 309B 30B3 3099]
```

結果を整理すると以下のようになります。
（具合の悪い箇所を赤くしています)

|Source|ｺﾞ<br>FF7A<br>FF9E|ｺ゛<br>FF7A<br>309B|ｺ゙<br>FF7A<br>3099|ゴ<br>30B4<br>&nbsp;|コﾞ<br>30B3<br>FF9E|コ゛<br>30B3<br>309B|ゴ<br>30B3<br>3099|問題点|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:--|
|Narrow|ｺﾞ<br>FF7A<br>FF9E|ｺ<font color="red">゛</font><br>FF7A<br><font color="red">309B</font>|ｺﾞ<br>FF7A<br>FF9E|<font color="red">ゴ<br>30B4<br>&nbsp;</font>|ｺﾞ<br>FF7A<br>FF9E|ｺ<font color="red">゛</font><br>FF7A<br><font color="red">309B</font>|ｺﾞ<br>FF7A<br>FF9E|Wide VOM(309B) が Narrow にならない。<br>Wide Precomp(30B4) が Narrow にならない。|
|Widen|<font color="red">ゴ<br>30B3<br>3099</font>|<font color="red">コ゛<br>30B3<br>309B</font>|<font color="red">ゴ<br>30B3<br>3099</font>|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B3<br>3099|コ゛<br>30B3<br>309B|ゴ<br>30B3<br>3099|3 つとも Precomp(30B4) になってほしい。<br>せめて Base + Combining (30B3+3099) に揃えてほしい。<br>
|Fold|<font color="red">ゴ<br>30B3<br>3099</font>|<font color="red">コ゛<br>30B3<br>309B</font>|<font color="red">ゴ<br>30B3<br>3099</font>|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B3<br>3099|コ゛<br>30B3<br>309B|ゴ<br>30B3<br>3099|Fold は Latin を Narrow に、Kana を Wide にする Transformer。問題点は Widen と同様。|

### golang.org/x/text/unicode/norm の問題点

もうひとつは golang.org/x/text/unicode/norm で、4 種類の Unicode Normalization Form をサポートしています。

**4 種類の Unicode 正規化形式**

|形式|説明|かな文字の場合|
|:--|:--|:--|
|NFD|正準等価性によって分解される。|濁点や半濁点が基底文字から分離する。Precomp => Base + Combining |
|NFC|正準等価性によって分解された後に、正準等価性によって合成される。|濁点や半濁点が基底文字から分離した後に再度合成される。<br>Precomp => Base + Combining,<br>Base + Combining => Precomp|
|NFKD|互換等価性によって分解される。|半角カタカナが全角カタカナになる。<br>Narrow => Wide|
|NFKC|互換等価性によって分解された後に、正準等価性によって合成される。|半角カタカナが全角カタカナに変換された後に、基底文字と合成文字が合成される。<br>Narrow => Wide,<br>Base + Combining => Precomp|

7 種類の「ゴ」を変換してみます。

```golang:norm_examples.go 
package main

import (
        "fmt"
        "golang.org/x/text/unicode/norm"
        "strings"
)

func hexs(s string) string {
    var ss []string
    for _, r := range []rune(s) {
        e := fmt.Sprintf("%04X", r)
        ss = append(ss, e)
    }
    switch len(ss) {
    case 0:
        return "<empty>"
    case 1:
        return "[" + ss[0] + "]"
    default:
        return "[" + strings.Join(ss, " ") + "]"
    }
}

func main() {
        s := "ｺﾞｺ゛ｺ\u3099ゴコﾞコ゛コ\u3099"
        nfd := norm.NFD.String(s)  // Canonical decomposition
        nfkd := norm.NFKD.String(s) // Compatibility decomposition
        nfc := norm.NFC.String(s)  // Canonical decomposition -> Canonical composition
        nfkc := norm.NFKC.String(s) // Compatibility decomposition -> Ccanonical composition

        fmt.Printf("SRC :\t%q,\n\t%s\n", s, hexs(s))
        fmt.Printf("NFD :\t%q,\n\t%s\n", nfd, hexs(nfd))
        fmt.Printf("NFKD:\t%q,\n\t%s\n", nfkd, hexs(nfkd))
        fmt.Printf("NFC :\t%q,\n\t%s\n", nfc, hexs(nfc))
        fmt.Printf("NFKC:\t%q,\n\t%s\n", nfkc, hexs(nfkc))

}
```

実行結果は以下のとおりです。

```bash:結果
$ go run norm_examples.go 
SRC :   "ｺﾞｺ゛ｺ゙ゴコﾞコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B4 30B3 FF9E 30B3 309B 30B3 3099]
NFD :   "ｺﾞｺ゛ｺ゙ゴコﾞコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B3 3099 30B3 FF9E 30B3 309B 30B3 3099]
NFKD:   "ゴコ ゙ゴゴゴコ ゙ゴ",
        [30B3 3099 30B3 0020 3099 30B3 3099 30B3 3099 30B3 3099 30B3 0020 3099 30B3 3099]
NFC :   "ｺﾞｺ゛ｺ゙ゴコﾞコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B4 30B3 FF9E 30B3 309B 30B4]
NFKC:   "ゴコ ゙ゴゴゴコ ゙ゴ",
        [30B4 30B3 0020 3099 30B4 30B4 30B4 30B3 0020 3099 30B4]
```

結果を整理すると以下のようになります。

|Source|ｺﾞ<br>FF7A<br>FF9E|ｺ゛<br>FF7A<br>309B|ｺ゙<br>FF7A<br>3099|ゴ<br>30B4<br>&nbsp;|コﾞ<br>30B3<br>FF9E|コ゛<br>30B3<br>309B|ゴ<br>30B3<br>3099|問題点|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:--|
|NFD|ｺ<font color="red">ﾞ</font><br>FF7A<br><font color="red">FF9E</font>|ｺ<font color="red">゛</font><br>FF7A<br><font color="red">309B</font>|ｺ゙<br>FF7A<br>3099|ゴ<br>30B3<br>3099|コ<font color="red">ﾞ</font><br>30B3<br><font color="red">FF9E</font>|コ<font color="red">゛</font><br>30B3<br><font color="red">309B</font>|ゴ<br>30B3<br>3099|Combining ではない VOM が無視される。|
|NFC|ｺ<font color="red">ﾞ</font><br>FF7A<br><font color="red">FF9E</font>|ｺ<font color="red">゛</font><br>FF7A<br><font color="red">309B</font>|ｺ゙<br>FF7A<br>3099|ゴ<br>30B4<br>&nbsp;|コ<font color="red">ﾞ</font><br>30B3<br><font color="red">FF9E</font>|コ<font color="red">゛</font><br>30B3<br><font color="red">309B</font>|ゴ<br>30B4<br>&nbsp;|NFD と同様。|
|NFKD|ゴ<br>30B3<br>3099|コ<font color="red"> ゙</font><br>30B3<br><font color="red">0020<br>3099</font>|ゴ<br>30B3<br>3099|ゴ<br>30B3<br>3099|ゴ<br>30B3<br>3099|コ<font color="red"> ゙</font><br>30B3<br><font color="red">0020<br>3099</font>|ゴ<br>30B3<br>3099|全角の VOM が Whitespace + Combining になる。|
|NFKC|ゴ<br>30B4<br>&nbsp;|コ<font color="red"> ゙</font><br>30B3<br><font color="red">0020<br>3099</font>|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|コ<font color="red"> ゙</font><br>30B3<br><font color="red">0020<br>3099</font>|ゴ<br>30B4<br>&nbsp;|全角の VOM が Wthitespace + Combining になるため、再合成されない。|

仕様通りに振舞っているのだとは思います。
でも、このパッケージをありがたいと感じている日本人は少ないのではないでしょうか。

# どんなライブラリを作ったのか？

日本語文字列ユーティリティです。
ノーマライザは、日本語のロケールで良く使われる 426 文字（ASCII 互換のラテン文字と JIS 互換のひらがなカタカナなど）をサポートしており、文字列の縦書き関数などを同梱しています。

## ダウンロード

ソースコードは GitHub に置いてあります。
<img width="300" alt="image.png" src="https://raw.githubusercontent.com/y-bash/go-gaga/master/gaga.png">
[go-gaga (Japanese language utility)](https://github.com/y-bash/go-gaga)
[README](https://github.com/y-bash/go-gaga/blob/master/README.md)

Golang の環境があれば、以下のコマンドでライブラリが使えるようになります。

```bash:console
$ go get github.com/y-bash/go-gaga
```

## 使い方

まず、正規化ルールをフラグで指定して Normalizer を生成します。
(エラー処理については後述します)

```golang
n, err := gaga.Norm(gaga.LatinToNarrow | gaga.KanaToWide)
```

あとは String() メソッドを呼ぶだけです。

```golang
s := n.String("変換したい文字列")
```

途中でフラグを変更することもできます。
(エラー処理については後述します)

```golang
err = n.SetFlag(gaga.LatinToWide)
s = n.String("変換したい文字列")
```

もう少し具体的なコードの例を示します。
以下のように文字種別ごとに半角全角の変換、濁点・半濁点の結合、分解、カタカナひらがなの変換、大文字小文字の変換が行えます。

```golang:gaga_examples.go
package main

import (
    "fmt"
    "github.com/y-bash/go-gaga"
)

func main() {
    s := "ｺﾞｺ゛ｺ\u3099ゴコﾞコ゛コ\u3099"
    n, _ := gaga.Norm(gaga.ComposeVom)   // 濁点と半濁点を適切に結合する
    fmt.Println(n.String(s))             // => ｺﾞｺﾞｺﾞゴゴゴゴ

    n, _ = gaga.Norm(gaga.KanaToNarrow)  // ひらがなとカタカナを半角にする
    fmt.Println(n.String(s))             // => ｺﾞｺﾞｺﾞｺﾞｺﾞｺﾞｺﾞ

    n, _ = gaga.Norm(gaga.KanaToWide)    // カタカナを全角にする
    fmt.Println(n.String(s))             // => ゴゴゴゴゴゴゴ

    n, _ = gaga.Norm(gaga.KanaToHiragana) // カタカナをひらがなにする
    fmt.Println(n.String(s))              // => ごごごごごごご

    s = "ａｂｃｱｲｳ"
    n, _ = gaga.Norm(
        gaga.LatinToNarrow | // ラテン文字を半角に、
        gaga.AlphaToUpper |  // アルファベットを大文字に、
        gaga.KanaToHiragana) // カタカナをひらがなにする
    fmt.Println(n.String(s)) // => ABCあいう
}
```

gaga で width や norm と同様のプログラムを書いてみます。

```golang:gaga_examples.go 
package main

import (
        "fmt"
        "github.com/y-bash/go-gaga"
        "strings"
)

func hexs(s string) string {
    var ss []string
    for _, r := range []rune(s) {
        e := fmt.Sprintf("%04X", r)
        ss = append(ss, e)
    }
    switch len(ss) {
    case 0:
        return "<empty>"
    case 1:
        return "[" + ss[0] + "]"
    default:
        return "[" + strings.Join(ss, " ") + "]"
    }
}

func main() {
        s := "ｺﾞｺ゛ｺ\u3099ゴコﾞコ゛コ\u3099"
        n, _ := gaga.Norm(gaga.ComposeVom)
        co := n.String(s)

        n.SetFlag(gaga.DecomposeVom)
        de:= n.String(s)

        n.SetFlag(gaga.KanaToNarrow)
        na:= n.String(s)

        n.SetFlag(gaga.KanaToWide)
        wi:= n.String(s)

        n.SetFlag(gaga.KanaToHiragana)
        hi := n.String(s)

        n.SetFlag(gaga.KatakanaToHiragana | gaga.DecomposeVom)
        hd := n.String(s)

        fmt.Printf("SRC            :%q,\n\t%s\n", s, hexs(s))
        fmt.Printf("ComposeVom     :%q,\n\t%s\n", co, hexs(co))
        fmt.Printf("DecomposeVom   :%q,\n\t%s\n", de, hexs(de))
        fmt.Printf("KanaToNarrow   :%q,\n\t%s\n", na, hexs(na))
        fmt.Printf("KanaToWide     :%q,\n\t%s\n", wi, hexs(wi))
        fmt.Printf("KanaToHiragana :%q,\n\t%s\n", hi, hexs(hi))
        fmt.Printf("Hiragana&DecVom:%q,\n\t%s\n", hd, hexs(hd))
}
```

結果は以下のとおりです。

```bash:結果
$ go run gaga_examples.go 
SRC            :"ｺﾞｺ゛ｺ゙ゴコﾞコ゛ゴ",
        [FF7A FF9E FF7A 309B FF7A 3099 30B4 30B3 FF9E 30B3 309B 30B3 3099]
ComposeVom     :"ｺﾞｺﾞｺﾞゴゴゴゴ",
        [FF7A FF9E FF7A FF9E FF7A FF9E 30B4 30B4 30B4 30B4]
DecomposeVom   :"ｺ゙ｺ゙ｺ゙ゴゴゴゴ",
        [FF7A 3099 FF7A 3099 FF7A 3099 30B3 3099 30B3 3099 30B3 3099 30B3 3099]
KanaToNarrow   :"ｺﾞｺﾞｺﾞｺﾞｺﾞｺﾞｺﾞ",
        [FF7A FF9E FF7A FF9E FF7A FF9E FF7A FF9E FF7A FF9E FF7A FF9E FF7A FF9E]
KanaToWide     :"ゴゴゴゴゴゴゴ",
        [30B4 30B4 30B4 30B4 30B4 30B4 30B4]
KanaToHiragana :"ごごごごごごご",
        [3054 3054 3054 3054 3054 3054 3054]
Hiragana&DecVom:"ごごごごごごご",
        [3053 3099 3053 3099 3053 3099 3053 3099 3053 3099 3053 3099 3053 3099]
```

結果を整理すると以下のようになります。

|Source|ｺﾞ<br>FF7A<br>FF9E|ｺ゛<br>FF7A<br>309B|ｺ゙<br>FF7A<br>3099|ゴ<br>30B4<br>&nbsp;|コﾞ<br>30B3<br>FF9E|コ゛<br>30B3<br>309B|ゴ<br>30B3<br>3099|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|ComposeVom|ｺﾞ<br>FF7A<br>FF9E|ｺﾞ<br>FF7A<br>FF9E|ｺﾞ<br>FF7A<br>FF9E|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|
|DecomposeVom|ｺ゙<br>FF7A<br>3099|ｺ゙<br>FF7A<br>3099|ｺ゙<br>FF7A<br>3099|ゴ<br>30B3<br>3099|ゴ<br>30B3<br>3099|ゴ<br>30B3<br>3099|ゴ<br>30B3<br>3099|
|KanaToNarrow|ｺﾞ<br>FF7A<br>FF9E|ｺﾞ<br>FF7A<br>FF9E|ｺﾞ<br>FF7A<br>FF9E|ｺﾞ<br>FF7A<br>FF9E|ｺﾞ<br>FF7A<br>FF9E|ｺﾞ<br>FF7A<br>FF9E|ｺﾞ<br>FF7A<br>FF9E|
|KanaToWide|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|ゴ<br>30B4<br>&nbsp;|
|KanaToHiragana|ご<br>3054<br>&nbsp;|ご<br>3054<br>&nbsp;|ご<br>3054<br>&nbsp;|ご<br>3054<br>&nbsp;|ご<br>3054<br>&nbsp;|ご<br>3054<br>&nbsp;|ご<br>3054<br>&nbsp;|
|KatakanaToHiragana &#124;<br>DecomposeVom|ご<br>3053<br>3099|ご<br>3053<br>3099|ご<br>3053<br>3099|ご<br>3053<br>3099|ご<br>3053<br>3099|ご<br>3053<br>3099|ご<br>3053<br>3099|


## エラーハンドリングについて

上記のコードでは見やすさのために省略していましたが、実際に使う際はエラーを適切にハンドリングする必要があります。gaga.Norm() も、gaga.Normalizer.SetFlag() も、存在しない値や排他的なフラグの組み合わせ（例えば KanaToNarrow | KanaToWide など）が指定されると error を返します。

以下にエラーハンドリングの例を記しておきます。

```golang:gaga_error.go
package main

import (
        "fmt"
        "github.com/y-bash/go-gaga"
        "log"
)

func main() {
        n, err := gaga.Norm(gaga.LatinToWide | gaga.AlphaToUpper)
        if err != nil {
          log.Fatal(err)
        }
        fmt.Println(n.String("aaa"))
        fmt.Println(n.String("bbb"))
        fmt.Println(n.String("ccc"))

        err = n.SetFlag(gaga.LatinToNarrow | gaga.SymbolToWide)
        if err != nil {
          log.Fatal(err)
        }
        fmt.Println(n.String("aaa"))
        fmt.Println(n.String("bbb"))
        fmt.Println(n.String("ccc"))
}
```

```bash:結果
$ go run gaga_error.go 
ＡＡＡ
ＢＢＢ
ＣＣＣ
2020/09/20 08:57:21 invalid normalization flag: (AlphaToNarrow | DigitToNarrow | SymbolToNarrow | SymbolToWide), invalid combination: (SymbolToNarrow | SymbolToWide)
exit status 1
```
## コマンドラインツール

go build や go install でライブラリと同じ機能を持つコマンドラインツールをビルドできます。
Linux と Windows で動作確認しています。
[norm コマンド README](https://github.com/y-bash/go-gaga/blob/master/cmd/norm/README.md)

## フラグの種類

変換ルールを指示するための 20 個のフラグ定数（単独フラグ）が用意されています。
ノーマライザを生成する際にこれを指定して使うのですが、その際、フラグをビット OR 演算子で組み合わせて指定することができます。

また、複数のフラグを組み合わせた定数（コンビネーションフラグ）が 8 個用意されているので、通常はこちらを使う方が便利でしょう。

**コンビネーションフラグ (8個)**

|フラグ|意味|同等の組み合わせ|
|:--|:--|:--|
|LatinToNarrow|アルファベット、ラテン数字、ラテンシンボルを半角にする。<br>【例】<br>"Ａ１？" => "A1?"|AlphaToNarrow &#124; DigitToNarrow &#124; SymbolToNarrow|
|LatinToWide|アルファベット、ラテン数字、ラテンシンボルを全角にする。<br>【例】<br>"A1?" => "Ａ１？"|AlphaToWide &#124; DigitToWide &#124; SymbolToWide|
|KanaToNarrow|ひらがな、カタカナ、仮名シンボルを半角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を半角にする。<br>【例】<br>"あイ、が゛" => "ｱｲ､ｶﾞﾞ"|HiraganaToNarrow &#124; KatakanaToNarrow &#124; KanaSymbolToNarrow &#124; IsolatedVomToNarrow &#124; ComposeVom|
|KanaToWide|カタカナ、仮名シンボルを全角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を全角にする。<br>【例】<br>"ｱ､ｶﾞﾞ" => "ア、ガ゛"|KatakanaToWide &#124; KanaSymbolToWide &#124; IsolatedVomToWide &#124; ComposeVom|
|KanaToWideKatakana|ひらがなを全角カタカナにする。半角カタカナを全角カタカナにする。仮名シンボルを全角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を全角にする。<br>【例】<br>"あｲ､ｶﾞﾞ" => "アイ、ガ゛"|KatakanaToWide &#124; HiraganaToKatakana &#124; KanaSymbolToWide &#124; IsolatedVomToWide &#124; ComposeVom|
|KanaToNarrowKatakana|ひらがなを半角カタカナにする。全角カタカナを半角カタカナにする。仮名シンボルを半角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を半角にする。<br>【例】<br>"あイ、が゛" => "ｱｲ､ｶﾞﾞ"|KatakanaToNarrow &#124; HiraganaToNarrow &#124; KanaSymbolToNarrow &#124; IsolatedVomToNarrow &#124; ComposeVom|
|KanaToHiragana|全角カタカナと半角カタカナをひらがなにする。仮名シンボルを全角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を全角にする。<br>【例】<br>"アｲ､ガ゛" => "あい、が゛"|KatakanaToHiragana &#124; KanaSymbolToWide &#124; IsolatedVomToWide &#124; ComposeVom|
|Fold|全角アルファベット、数字、シンボルを半角にする。半角カタカナを全角カタカナにする。半角仮名シンボルを全角にする。濁点および半濁点を従来の方法で結合する。独立した濁点半濁点を全角にする。<br>【例】<br>"Ａ１？ｱ､ｶﾞﾞ" => "A1?ア、ガ゛"|LatinToNarrow &#124; KanaToWide|

**単独フラグ (20個)**

|フラグ|意味|
|:--|:--|
|AlphaToNarrow|全角アルファベットを半角にする。<br>【例】<br>[Ａ]=>[A]|
|AlphaToWide|半角アルファベットを全角にする。<br>【例】<br>[A]=>[Ａ] |
|AlphaToUpper|半角アルファベットと全角アルファベットを大文字にする。<br>【例】<br>[a]=>[A], [ａ]=>[Ａ]|
|AlphaToLower|半角アルファベットと全角アルファベットを小文字にする。<br>【例】<br>[A] => [a],  [Ａ] => [ａ]|
|DigitToNarrow|全角ラテン数字を半角にする。<br>【例】<br>[１]=>[1]|
|DigitToWide|半角ラテン数字を全角にする。<br>【例】<br>[1]=>[１]|
|SymbolToNarrow|全角ラテン・シンボルを半角にする。<br>【例】<br>[？]=>[?]|
|SymbolToWide|半角ラテン・シンボルを全角にする。<br>【例】<br>[?]=>[？]|
|HiraganaToNarrow|ひらがなを半角カタカナにする。<br>【例】<br>[あ]=>[ｱ]|
|HiraganaToKatakana|ひらがなを全角カタカナにする。<br>【例】<br>[あ]=>[ア]|
|KatakanaToNarrow|全角カタカナを半角にする。<br>【例】<br>[ア]=>[ｱ]|
|KatakanaToWide|半角カタカナを全角にする。<br>【例】<br>[ｱ]=>[ア]|
|KatakanaToHiragana|半角カタカナと全角カタカナをひらがなにする。<br>【例】<br>[ア] => [あ]<br>[ｱ] => [あ]|
|KanaSymbolToNarrow|全角仮名シンボルを半角にする。<br>【例】<br>[、]=>[､]|
|KanaSymbolToWide|半角仮名シンボルを全角にする。<br>【例】<br>[､]=>[、]|
|ComposeVom|濁点と半濁点を従来の方法で結合する。<br>【例】<br>[か][゛]=>[が]<br>[か][U+3099] => [が]<br>[か][ﾞ]=>[が]<br>[ｶ][゛]=>[ｶ][ﾞ]<br>[ｶ][ﾞ]=>[ｶ][ﾞ]<br>[ｶ][U+3099]=>[ｶ][ﾞ]<br>[は][゜]=>[ぱ]<br>[ヰ][゛]=>[ヸ]<br><br>Pre-composed character を持たないひらがなカタカナに続く濁点と半濁点は、先行する文字と同じ幅に変換される。<br>[あ][ﾞ]=>[あ][゛]<br>[あ][U+3099]=>[あ][゛]<br>[ｱ][゛]=>[ｱ][ﾞ]<br>[ｱ][U+3099]=>[ｱ][ﾞ]<br>[ゐ][゛]=>[ゐ][゛]<br><br>Pre-composed character に続く濁点と半濁点はこのフラグでは変換されない。<br>【例】[が][゛]=>[が][゛]<br>[が][U+3099]=>[が][U+3099]<br>[が][ﾞ]=>[が][ﾞ]<br><br>ひらがなカタカナ以外の文字に続く濁点と半濁点はこのフラグでは変換されない。<br>【例】<br>[日][゛]=>[日][゛]<br>[日][U+3099]=>[日][U+3099]<br>[日][ﾞ]=>[日][ﾞ]<br>|
|DecomposeVom|濁点と半濁点を Unicode Normalization Form の正準等価による分解に似た方法で分解する。<br>【例】<br>[が]=>[か][U+3099]<br>[か][゛]=>[か][U+3099]<br>[か][ﾞ]=>[か][U+3099]<br>[ｶ][゛]=>[ｶ][U+3099]<br>[ｶ][ﾞ]=>[ｶ][U+3099]<br>[は][゜]=>[は][U+309A]<br>[あ][゛]=>[あ][U+3099]<br><br><br>Pre-composed character に続く濁点と半濁点はこのフラグでは変換されない。<br><br>ひらがなカタカナ以外の文字に続く濁点と半濁点はこのフラグでは変換されない。|
|IsolatedVomToNarrow|Base character に接続されない独立した濁点と半濁点を半角にする。<br>【例】<br>[゛]=>[ﾞ]<br>[U+3099]=>[ﾞ]<br>[゜]=>[ﾟ]<br>[U+309A]=>[ﾟ]|
|IsolatedVomToWide|Base character に接続されない独立した濁点と半濁点を全角にする。<br>【例】<br>[ﾞ]=>[゛]<br>[U+3099]=>[゛]<br>[ﾟ]=>[゜]<br>[U+309A]=>[゜]|
|IsolatedVomToNonspace|Base character に接続されない独立した濁点と半濁点を Non-space (Combining character) にする。<br>【例】<br>[゛]=>[U+3099]<br>[ﾞ]=>[U+3099]<br>[゜]=>[U+309A]<br>[ﾟ]=>[U+309A]|

# どのように実装したのか？

[The Unicode Consortium](https://home.unicode.org/) が提供する [UCD in XML (Unicode Character Database in XML)](https://unicode.org/Public/13.0.0/ucdxml/) を元に Golang 用のテーブルを生成しています。

[ジェネレータのコード](https://github.com/y-bash/go-gaga/tree/master/gen)
[ジェネレータが生成したテーブル (unichar_tables.go)](https://github.com/y-bash/go-gaga/blob/master/unichar_tables.go)

コード生成の大まかな流れは以下のとおりです。

**コード生成シーケンス**

|No.|プロセス|補足|
|--:|:--|:--|
|1.|zip 形式ファイルのダウンロード||
|2.|zip 解凍、XML ファイル (UCD) の取り出し||
|3.|XML ファイルのパース|
|4.|パースした XML を CSV として出力|確認用 Utility<br>(ソースコード生成とは別のコマンド)|
|5.|UCD に足りない情報の付与|追加した情報<br>- ひらがな-カタカナのリレーション、<br>- 全角小書き文字-半角並字のリレーションなど|
|6.|文字間の双方向リンクの作成|UCD のひらがなカタカナ情報には片方向のリンクしかない。<br>- Narrow->Wide（ｱ->ア など)<br>- Precomp->Base,Combining (ガ->カ,◌゙ など)<br>※ラテン文字の Uppercase と Lowercase については双方向のリンク情報がある|
|7.|拡張 UCD (5, 6 を施したもの)を CSV 出力|確認用 Utility<br>(ソースコード生成とは別のコマンド)<br>多言語ライブラリへの移植用としての活用も想定<br>(Rust, TypeScript/JavaScript, etc...)|
|8.|拡張 UCD を Golang のテーブルとして出力|with go fmt|

なお、コード生成については以下の情報を参考にさせていただきました。

* [yaegashi さん](https://qiita.com/yaegashi) の記事 - [go generate のベストプラクティス](https://qiita.com/yaegashi/items/d1fd9f7d0c75b2bb7446)
* [mattn さん](https://qiita.com/mattn) のライブラリ - [go-runewidth](https://github.com/mattn/go-runewidth)

yaegashi さんの記事は、ジェネレータを含むプロジェクトのリポジトリ構成をどうすべきかという点について参考にさせていただきました。

mattn さんの go-runewidth からも多くの事を学ばせていただきました。中でも生成したテーブル構造のチェックサムをテストするアイデアを真似させていただき、これがとても気に入ってます。これを行うことで、万一、元ネタ（ダウンロードデータ）の内容が変更されたり、ジェネレータをリファクタリングした際にうっかり生成コードを壊してしまうようなことがあってもすぐ気づくことができます。

また、GaGa に同梱した [縦書きコマンド vert](https://github.com/y-bash/go-gaga/blob/master/cmd/vert/README.md) の中で go-runewidth を使わせていただいています。

お二人ともありがとうございました。

# パフォーマンスは良いのか？

パフォーマンスは悪くないと思います。
というか、VOM の結合と分解をしているところ以外は、アルゴリズム的にほぼ何もしていません（笑）

Unicode のブロックごとに 4 つのテーブル (配列) があり（連続するブロックは 1 つにまとめています）、変換する文字がこれのどれにあたるのかを線形検索しているところの平均計算量が **O(2)**、
テーブルが決まってからは、変換する文字のコードポイント（rune の値）からオフセット値を計算、それを配列のインデックスにして要素を取り出しているところの計算量が **O(1)** です。

ベンチマークのコードと結果は以下のとおりです。

```golang:benchmark
const normSTR = "Aa#　Ａａ＃あア。ｱ｡”ﾞ漢字ｶﾞｷﾞｸﾞｹﾞｺﾞﾊﾟﾋﾟﾌﾟﾍﾟﾎﾟ"

func BenchmarkString(b *testing.B) {
        b.ResetTimer()
        n, _ := Norm(LatinToNarrow | KanaToWide)
        for i := 0; i < b.N; i++ {
                n.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkNormNFKD(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                norm.NFKD.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkNormNFKC(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                norm.NFKC.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkWidthNarrow(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                width.Narrow.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkWidthWiden(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                width.Widen.String(normSTR)
        }
        b.StopTimer()
}

func BenchmarkWidthFold(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
                width.Fold.String(normSTR)
        }
        b.StopTimer()
}
```

```bash:benchmark_result
$ make bench
go test -v -run "Benchmark" -bench . -benchmem -o ./output/bench.bin -cpuprofile=./output/cpu.prof -memprofile=./output/mem.prof
goos: linux
goarch: amd64
pkg: github.com/y-bash/go-gaga
BenchmarkString
BenchmarkString-2                 713604              1683 ns/op             224 B/op          2 allocs/op
BenchmarkNormNFKD
BenchmarkNormNFKD-2               189483              6348 ns/op             784 B/op          3 allocs/op
BenchmarkNormNFKC
BenchmarkNormNFKC-2               165166              7101 ns/op             752 B/op          3 allocs/op
BenchmarkWidthNarrow
BenchmarkWidthNarrow-2           1395560               892 ns/op             368 B/op          3 allocs/op
BenchmarkWidthWiden
BenchmarkWidthWiden-2            1392997               854 ns/op             384 B/op          3 allocs/op
BenchmarkWidthFold
BenchmarkWidthFold-2             1452519               822 ns/op             368 B/op          3 allocs/op
```

上記結果を gaga を 100 として比較すると以下のようになります。

|パッケージ|処理時間|メモリ使用量|アロケーション回数|
|:--|--:|--:|--:|
|gaga|100|100|100|
|norm.NFKD|377|350|150|
|norm.NFKC|422|336|150|
|width.Narrow|53|164|150|
|width.Widen|51|171|150|
|width.Fold|49|164|150|

norm パッケージの 3 〜 4 倍の速度が出ているので悪くないと思います。
width は速いですね。VOMの結合と分解をしていないからですかね …

# GaGa がサポートする 462文字

ライブラリがサポートする文字の一覧表を挙げておきます。

<details><summary>一覧表を展開する</summary><div>

|Block|Name|Age|Codepoint|Glyph|
|:-:|:--|:-:|:-:|:-:|:-:|
|ASCII|SPACE|1.1|U+0020| |
|ASCII|EXCLAMATION MARK|1.1|U+0021|!|
|ASCII|QUOTATION MARK|1.1|U+0022|"|
|ASCII|NUMBER SIGN|1.1|U+0023|#|
|ASCII|DOLLAR SIGN|1.1|U+0024|$|
|ASCII|PERCENT SIGN|1.1|U+0025|%|
|ASCII|AMPERSAND|1.1|U+0026|&|
|ASCII|APOSTROPHE|1.1|U+0027|'|
|ASCII|LEFT PARENTHESIS|1.1|U+0028|(|
|ASCII|RIGHT PARENTHESIS|1.1|U+0029|)|
|ASCII|ASTERISK|1.1|U+002A|*|
|ASCII|PLUS SIGN|1.1|U+002B|+|
|ASCII|COMMA|1.1|U+002C|,|
|ASCII|HYPHEN-MINUS|1.1|U+002D|-|
|ASCII|FULL STOP|1.1|U+002E|.|
|ASCII|SOLIDUS|1.1|U+002F|/|
|ASCII|DIGIT ZERO|1.1|U+0030|0|
|ASCII|DIGIT ONE|1.1|U+0031|1|
|ASCII|DIGIT TWO|1.1|U+0032|2|
|ASCII|DIGIT THREE|1.1|U+0033|3|
|ASCII|DIGIT FOUR|1.1|U+0034|4|
|ASCII|DIGIT FIVE|1.1|U+0035|5|
|ASCII|DIGIT SIX|1.1|U+0036|6|
|ASCII|DIGIT SEVEN|1.1|U+0037|7|
|ASCII|DIGIT EIGHT|1.1|U+0038|8|
|ASCII|DIGIT NINE|1.1|U+0039|9|
|ASCII|COLON|1.1|U+003A|:|
|ASCII|SEMICOLON|1.1|U+003B|;|
|ASCII|LESS-THAN SIGN|1.1|U+003C|<|
|ASCII|EQUALS SIGN|1.1|U+003D|=|
|ASCII|GREATER-THAN SIGN|1.1|U+003E|>|
|ASCII|QUESTION MARK|1.1|U+003F|?|
|ASCII|COMMERCIAL AT|1.1|U+0040|@|
|ASCII|LATIN CAPITAL LETTER A|1.1|U+0041|A|
|ASCII|LATIN CAPITAL LETTER B|1.1|U+0042|B|
|ASCII|LATIN CAPITAL LETTER C|1.1|U+0043|C|
|ASCII|LATIN CAPITAL LETTER D|1.1|U+0044|D|
|ASCII|LATIN CAPITAL LETTER E|1.1|U+0045|E|
|ASCII|LATIN CAPITAL LETTER F|1.1|U+0046|F|
|ASCII|LATIN CAPITAL LETTER G|1.1|U+0047|G|
|ASCII|LATIN CAPITAL LETTER H|1.1|U+0048|H|
|ASCII|LATIN CAPITAL LETTER I|1.1|U+0049|I|
|ASCII|LATIN CAPITAL LETTER J|1.1|U+004A|J|
|ASCII|LATIN CAPITAL LETTER K|1.1|U+004B|K|
|ASCII|LATIN CAPITAL LETTER L|1.1|U+004C|L|
|ASCII|LATIN CAPITAL LETTER M|1.1|U+004D|M|
|ASCII|LATIN CAPITAL LETTER N|1.1|U+004E|N|
|ASCII|LATIN CAPITAL LETTER O|1.1|U+004F|O|
|ASCII|LATIN CAPITAL LETTER P|1.1|U+0050|P|
|ASCII|LATIN CAPITAL LETTER Q|1.1|U+0051|Q|
|ASCII|LATIN CAPITAL LETTER R|1.1|U+0052|R|
|ASCII|LATIN CAPITAL LETTER S|1.1|U+0053|S|
|ASCII|LATIN CAPITAL LETTER T|1.1|U+0054|T|
|ASCII|LATIN CAPITAL LETTER U|1.1|U+0055|U|
|ASCII|LATIN CAPITAL LETTER V|1.1|U+0056|V|
|ASCII|LATIN CAPITAL LETTER W|1.1|U+0057|W|
|ASCII|LATIN CAPITAL LETTER X|1.1|U+0058|X|
|ASCII|LATIN CAPITAL LETTER Y|1.1|U+0059|Y|
|ASCII|LATIN CAPITAL LETTER Z|1.1|U+005A|Z|
|ASCII|LEFT SQUARE BRACKET|1.1|U+005B|[|
|ASCII|REVERSE SOLIDUS|1.1|U+005C|\|
|ASCII|RIGHT SQUARE BRACKET|1.1|U+005D|]|
|ASCII|CIRCUMFLEX ACCENT|1.1|U+005E|^|
|ASCII|LOW LINE|1.1|U+005F|_|
|ASCII|GRAVE ACCENT|1.1|U+0060|`|
|ASCII|LATIN SMALL LETTER A|1.1|U+0061|a|
|ASCII|LATIN SMALL LETTER B|1.1|U+0062|b|
|ASCII|LATIN SMALL LETTER C|1.1|U+0063|c|
|ASCII|LATIN SMALL LETTER D|1.1|U+0064|d|
|ASCII|LATIN SMALL LETTER E|1.1|U+0065|e|
|ASCII|LATIN SMALL LETTER F|1.1|U+0066|f|
|ASCII|LATIN SMALL LETTER G|1.1|U+0067|g|
|ASCII|LATIN SMALL LETTER H|1.1|U+0068|h|
|ASCII|LATIN SMALL LETTER I|1.1|U+0069|i|
|ASCII|LATIN SMALL LETTER J|1.1|U+006A|j|
|ASCII|LATIN SMALL LETTER K|1.1|U+006B|k|
|ASCII|LATIN SMALL LETTER L|1.1|U+006C|l|
|ASCII|LATIN SMALL LETTER M|1.1|U+006D|m|
|ASCII|LATIN SMALL LETTER N|1.1|U+006E|n|
|ASCII|LATIN SMALL LETTER O|1.1|U+006F|o|
|ASCII|LATIN SMALL LETTER P|1.1|U+0070|p|
|ASCII|LATIN SMALL LETTER Q|1.1|U+0071|q|
|ASCII|LATIN SMALL LETTER R|1.1|U+0072|r|
|ASCII|LATIN SMALL LETTER S|1.1|U+0073|s|
|ASCII|LATIN SMALL LETTER T|1.1|U+0074|t|
|ASCII|LATIN SMALL LETTER U|1.1|U+0075|u|
|ASCII|LATIN SMALL LETTER V|1.1|U+0076|v|
|ASCII|LATIN SMALL LETTER W|1.1|U+0077|w|
|ASCII|LATIN SMALL LETTER X|1.1|U+0078|x|
|ASCII|LATIN SMALL LETTER Y|1.1|U+0079|y|
|ASCII|LATIN SMALL LETTER Z|1.1|U+007A|z|
|ASCII|LEFT CURLY BRACKET|1.1|U+007B|{|
|ASCII|VERTICAL LINE|1.1|U+007C|||
|ASCII|RIGHT CURLY BRACKET|1.1|U+007D|}|
|ASCII|TILDE|1.1|U+007E|~|
|CJK_Symbols|IDEOGRAPHIC SPACE|1.1|U+3000|　|
|CJK_Symbols|IDEOGRAPHIC COMMA|1.1|U+3001|、|
|CJK_Symbols|IDEOGRAPHIC FULL STOP|1.1|U+3002|。|
|CJK_Symbols|LEFT CORNER BRACKET|1.1|U+300C|「|
|CJK_Symbols|RIGHT CORNER BRACKET|1.1|U+300D|」|
|Hiragana|HIRAGANA LETTER SMALL A|1.1|U+3041|ぁ|
|Hiragana|HIRAGANA LETTER A|1.1|U+3042|あ|
|Hiragana|HIRAGANA LETTER SMALL I|1.1|U+3043|ぃ|
|Hiragana|HIRAGANA LETTER I|1.1|U+3044|い|
|Hiragana|HIRAGANA LETTER SMALL U|1.1|U+3045|ぅ|
|Hiragana|HIRAGANA LETTER U|1.1|U+3046|う|
|Hiragana|HIRAGANA LETTER SMALL E|1.1|U+3047|ぇ|
|Hiragana|HIRAGANA LETTER E|1.1|U+3048|え|
|Hiragana|HIRAGANA LETTER SMALL O|1.1|U+3049|ぉ|
|Hiragana|HIRAGANA LETTER O|1.1|U+304A|お|
|Hiragana|HIRAGANA LETTER KA|1.1|U+304B|か|
|Hiragana|HIRAGANA LETTER GA|1.1|U+304C|が|
|Hiragana|HIRAGANA LETTER KI|1.1|U+304D|き|
|Hiragana|HIRAGANA LETTER GI|1.1|U+304E|ぎ|
|Hiragana|HIRAGANA LETTER KU|1.1|U+304F|く|
|Hiragana|HIRAGANA LETTER GU|1.1|U+3050|ぐ|
|Hiragana|HIRAGANA LETTER KE|1.1|U+3051|け|
|Hiragana|HIRAGANA LETTER GE|1.1|U+3052|げ|
|Hiragana|HIRAGANA LETTER KO|1.1|U+3053|こ|
|Hiragana|HIRAGANA LETTER GO|1.1|U+3054|ご|
|Hiragana|HIRAGANA LETTER SA|1.1|U+3055|さ|
|Hiragana|HIRAGANA LETTER ZA|1.1|U+3056|ざ|
|Hiragana|HIRAGANA LETTER SI|1.1|U+3057|し|
|Hiragana|HIRAGANA LETTER ZI|1.1|U+3058|じ|
|Hiragana|HIRAGANA LETTER SU|1.1|U+3059|す|
|Hiragana|HIRAGANA LETTER ZU|1.1|U+305A|ず|
|Hiragana|HIRAGANA LETTER SE|1.1|U+305B|せ|
|Hiragana|HIRAGANA LETTER ZE|1.1|U+305C|ぜ|
|Hiragana|HIRAGANA LETTER SO|1.1|U+305D|そ|
|Hiragana|HIRAGANA LETTER ZO|1.1|U+305E|ぞ|
|Hiragana|HIRAGANA LETTER TA|1.1|U+305F|た|
|Hiragana|HIRAGANA LETTER DA|1.1|U+3060|だ|
|Hiragana|HIRAGANA LETTER TI|1.1|U+3061|ち|
|Hiragana|HIRAGANA LETTER DI|1.1|U+3062|ぢ|
|Hiragana|HIRAGANA LETTER SMALL TU|1.1|U+3063|っ|
|Hiragana|HIRAGANA LETTER TU|1.1|U+3064|つ|
|Hiragana|HIRAGANA LETTER DU|1.1|U+3065|づ|
|Hiragana|HIRAGANA LETTER TE|1.1|U+3066|て|
|Hiragana|HIRAGANA LETTER DE|1.1|U+3067|で|
|Hiragana|HIRAGANA LETTER TO|1.1|U+3068|と|
|Hiragana|HIRAGANA LETTER DO|1.1|U+3069|ど|
|Hiragana|HIRAGANA LETTER NA|1.1|U+306A|な|
|Hiragana|HIRAGANA LETTER NI|1.1|U+306B|に|
|Hiragana|HIRAGANA LETTER NU|1.1|U+306C|ぬ|
|Hiragana|HIRAGANA LETTER NE|1.1|U+306D|ね|
|Hiragana|HIRAGANA LETTER NO|1.1|U+306E|の|
|Hiragana|HIRAGANA LETTER HA|1.1|U+306F|は|
|Hiragana|HIRAGANA LETTER BA|1.1|U+3070|ば|
|Hiragana|HIRAGANA LETTER PA|1.1|U+3071|ぱ|
|Hiragana|HIRAGANA LETTER HI|1.1|U+3072|ひ|
|Hiragana|HIRAGANA LETTER BI|1.1|U+3073|び|
|Hiragana|HIRAGANA LETTER PI|1.1|U+3074|ぴ|
|Hiragana|HIRAGANA LETTER HU|1.1|U+3075|ふ|
|Hiragana|HIRAGANA LETTER BU|1.1|U+3076|ぶ|
|Hiragana|HIRAGANA LETTER PU|1.1|U+3077|ぷ|
|Hiragana|HIRAGANA LETTER HE|1.1|U+3078|へ|
|Hiragana|HIRAGANA LETTER BE|1.1|U+3079|べ|
|Hiragana|HIRAGANA LETTER PE|1.1|U+307A|ぺ|
|Hiragana|HIRAGANA LETTER HO|1.1|U+307B|ほ|
|Hiragana|HIRAGANA LETTER BO|1.1|U+307C|ぼ|
|Hiragana|HIRAGANA LETTER PO|1.1|U+307D|ぽ|
|Hiragana|HIRAGANA LETTER MA|1.1|U+307E|ま|
|Hiragana|HIRAGANA LETTER MI|1.1|U+307F|み|
|Hiragana|HIRAGANA LETTER MU|1.1|U+3080|む|
|Hiragana|HIRAGANA LETTER ME|1.1|U+3081|め|
|Hiragana|HIRAGANA LETTER MO|1.1|U+3082|も|
|Hiragana|HIRAGANA LETTER SMALL YA|1.1|U+3083|ゃ|
|Hiragana|HIRAGANA LETTER YA|1.1|U+3084|や|
|Hiragana|HIRAGANA LETTER SMALL YU|1.1|U+3085|ゅ|
|Hiragana|HIRAGANA LETTER YU|1.1|U+3086|ゆ|
|Hiragana|HIRAGANA LETTER SMALL YO|1.1|U+3087|ょ|
|Hiragana|HIRAGANA LETTER YO|1.1|U+3088|よ|
|Hiragana|HIRAGANA LETTER RA|1.1|U+3089|ら|
|Hiragana|HIRAGANA LETTER RI|1.1|U+308A|り|
|Hiragana|HIRAGANA LETTER RU|1.1|U+308B|る|
|Hiragana|HIRAGANA LETTER RE|1.1|U+308C|れ|
|Hiragana|HIRAGANA LETTER RO|1.1|U+308D|ろ|
|Hiragana|HIRAGANA LETTER SMALL WA|1.1|U+308E|ゎ|
|Hiragana|HIRAGANA LETTER WA|1.1|U+308F|わ|
|Hiragana|HIRAGANA LETTER WI|1.1|U+3090|ゐ|
|Hiragana|HIRAGANA LETTER WE|1.1|U+3091|ゑ|
|Hiragana|HIRAGANA LETTER WO|1.1|U+3092|を|
|Hiragana|HIRAGANA LETTER N|1.1|U+3093|ん|
|Hiragana|HIRAGANA LETTER VU|1.1|U+3094|ゔ|
|Hiragana|HIRAGANA LETTER SMALL KA|3.2|U+3095|ゕ|
|Hiragana|HIRAGANA LETTER SMALL KE|3.2|U+3096|ゖ|
|Hiragana|COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK|1.1|U+3099|゙|
|Hiragana|COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK|1.1|U+309A|゚|
|Hiragana|KATAKANA-HIRAGANA VOICED SOUND MARK|1.1|U+309B|゛|
|Hiragana|KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK|1.1|U+309C|゜|
|Hiragana|HIRAGANA ITERATION MARK|1.1|U+309D|ゝ|
|Hiragana|HIRAGANA VOICED ITERATION MARK|1.1|U+309E|ゞ|
|Hiragana|HIRAGANA DIGRAPH YORI|3.2|U+309F|ゟ|
|Katakana|KATAKANA-HIRAGANA DOUBLE HYPHEN|3.2|U+30A0|゠|
|Katakana|KATAKANA LETTER SMALL A|1.1|U+30A1|ァ|
|Katakana|KATAKANA LETTER A|1.1|U+30A2|ア|
|Katakana|KATAKANA LETTER SMALL I|1.1|U+30A3|ィ|
|Katakana|KATAKANA LETTER I|1.1|U+30A4|イ|
|Katakana|KATAKANA LETTER SMALL U|1.1|U+30A5|ゥ|
|Katakana|KATAKANA LETTER U|1.1|U+30A6|ウ|
|Katakana|KATAKANA LETTER SMALL E|1.1|U+30A7|ェ|
|Katakana|KATAKANA LETTER E|1.1|U+30A8|エ|
|Katakana|KATAKANA LETTER SMALL O|1.1|U+30A9|ォ|
|Katakana|KATAKANA LETTER O|1.1|U+30AA|オ|
|Katakana|KATAKANA LETTER KA|1.1|U+30AB|カ|
|Katakana|KATAKANA LETTER GA|1.1|U+30AC|ガ|
|Katakana|KATAKANA LETTER KI|1.1|U+30AD|キ|
|Katakana|KATAKANA LETTER GI|1.1|U+30AE|ギ|
|Katakana|KATAKANA LETTER KU|1.1|U+30AF|ク|
|Katakana|KATAKANA LETTER GU|1.1|U+30B0|グ|
|Katakana|KATAKANA LETTER KE|1.1|U+30B1|ケ|
|Katakana|KATAKANA LETTER GE|1.1|U+30B2|ゲ|
|Katakana|KATAKANA LETTER KO|1.1|U+30B3|コ|
|Katakana|KATAKANA LETTER GO|1.1|U+30B4|ゴ|
|Katakana|KATAKANA LETTER SA|1.1|U+30B5|サ|
|Katakana|KATAKANA LETTER ZA|1.1|U+30B6|ザ|
|Katakana|KATAKANA LETTER SI|1.1|U+30B7|シ|
|Katakana|KATAKANA LETTER ZI|1.1|U+30B8|ジ|
|Katakana|KATAKANA LETTER SU|1.1|U+30B9|ス|
|Katakana|KATAKANA LETTER ZU|1.1|U+30BA|ズ|
|Katakana|KATAKANA LETTER SE|1.1|U+30BB|セ|
|Katakana|KATAKANA LETTER ZE|1.1|U+30BC|ゼ|
|Katakana|KATAKANA LETTER SO|1.1|U+30BD|ソ|
|Katakana|KATAKANA LETTER ZO|1.1|U+30BE|ゾ|
|Katakana|KATAKANA LETTER TA|1.1|U+30BF|タ|
|Katakana|KATAKANA LETTER DA|1.1|U+30C0|ダ|
|Katakana|KATAKANA LETTER TI|1.1|U+30C1|チ|
|Katakana|KATAKANA LETTER DI|1.1|U+30C2|ヂ|
|Katakana|KATAKANA LETTER SMALL TU|1.1|U+30C3|ッ|
|Katakana|KATAKANA LETTER TU|1.1|U+30C4|ツ|
|Katakana|KATAKANA LETTER DU|1.1|U+30C5|ヅ|
|Katakana|KATAKANA LETTER TE|1.1|U+30C6|テ|
|Katakana|KATAKANA LETTER DE|1.1|U+30C7|デ|
|Katakana|KATAKANA LETTER TO|1.1|U+30C8|ト|
|Katakana|KATAKANA LETTER DO|1.1|U+30C9|ド|
|Katakana|KATAKANA LETTER NA|1.1|U+30CA|ナ|
|Katakana|KATAKANA LETTER NI|1.1|U+30CB|ニ|
|Katakana|KATAKANA LETTER NU|1.1|U+30CC|ヌ|
|Katakana|KATAKANA LETTER NE|1.1|U+30CD|ネ|
|Katakana|KATAKANA LETTER NO|1.1|U+30CE|ノ|
|Katakana|KATAKANA LETTER HA|1.1|U+30CF|ハ|
|Katakana|KATAKANA LETTER BA|1.1|U+30D0|バ|
|Katakana|KATAKANA LETTER PA|1.1|U+30D1|パ|
|Katakana|KATAKANA LETTER HI|1.1|U+30D2|ヒ|
|Katakana|KATAKANA LETTER BI|1.1|U+30D3|ビ|
|Katakana|KATAKANA LETTER PI|1.1|U+30D4|ピ|
|Katakana|KATAKANA LETTER HU|1.1|U+30D5|フ|
|Katakana|KATAKANA LETTER BU|1.1|U+30D6|ブ|
|Katakana|KATAKANA LETTER PU|1.1|U+30D7|プ|
|Katakana|KATAKANA LETTER HE|1.1|U+30D8|ヘ|
|Katakana|KATAKANA LETTER BE|1.1|U+30D9|ベ|
|Katakana|KATAKANA LETTER PE|1.1|U+30DA|ペ|
|Katakana|KATAKANA LETTER HO|1.1|U+30DB|ホ|
|Katakana|KATAKANA LETTER BO|1.1|U+30DC|ボ|
|Katakana|KATAKANA LETTER PO|1.1|U+30DD|ポ|
|Katakana|KATAKANA LETTER MA|1.1|U+30DE|マ|
|Katakana|KATAKANA LETTER MI|1.1|U+30DF|ミ|
|Katakana|KATAKANA LETTER MU|1.1|U+30E0|ム|
|Katakana|KATAKANA LETTER ME|1.1|U+30E1|メ|
|Katakana|KATAKANA LETTER MO|1.1|U+30E2|モ|
|Katakana|KATAKANA LETTER SMALL YA|1.1|U+30E3|ャ|
|Katakana|KATAKANA LETTER YA|1.1|U+30E4|ヤ|
|Katakana|KATAKANA LETTER SMALL YU|1.1|U+30E5|ュ|
|Katakana|KATAKANA LETTER YU|1.1|U+30E6|ユ|
|Katakana|KATAKANA LETTER SMALL YO|1.1|U+30E7|ョ|
|Katakana|KATAKANA LETTER YO|1.1|U+30E8|ヨ|
|Katakana|KATAKANA LETTER RA|1.1|U+30E9|ラ|
|Katakana|KATAKANA LETTER RI|1.1|U+30EA|リ|
|Katakana|KATAKANA LETTER RU|1.1|U+30EB|ル|
|Katakana|KATAKANA LETTER RE|1.1|U+30EC|レ|
|Katakana|KATAKANA LETTER RO|1.1|U+30ED|ロ|
|Katakana|KATAKANA LETTER SMALL WA|1.1|U+30EE|ヮ|
|Katakana|KATAKANA LETTER WA|1.1|U+30EF|ワ|
|Katakana|KATAKANA LETTER WI|1.1|U+30F0|ヰ|
|Katakana|KATAKANA LETTER WE|1.1|U+30F1|ヱ|
|Katakana|KATAKANA LETTER WO|1.1|U+30F2|ヲ|
|Katakana|KATAKANA LETTER N|1.1|U+30F3|ン|
|Katakana|KATAKANA LETTER VU|1.1|U+30F4|ヴ|
|Katakana|KATAKANA LETTER SMALL KA|1.1|U+30F5|ヵ|
|Katakana|KATAKANA LETTER SMALL KE|1.1|U+30F6|ヶ|
|Katakana|KATAKANA LETTER VA|1.1|U+30F7|ヷ|
|Katakana|KATAKANA LETTER VI|1.1|U+30F8|ヸ|
|Katakana|KATAKANA LETTER VE|1.1|U+30F9|ヹ|
|Katakana|KATAKANA LETTER VO|1.1|U+30FA|ヺ|
|Katakana|KATAKANA MIDDLE DOT|1.1|U+30FB|・|
|Katakana|KATAKANA-HIRAGANA PROLONGED SOUND MARK|1.1|U+30FC|ー|
|Katakana|KATAKANA ITERATION MARK|1.1|U+30FD|ヽ|
|Katakana|KATAKANA VOICED ITERATION MARK|1.1|U+30FE|ヾ|
|Katakana|KATAKANA DIGRAPH KOTO|3.2|U+30FF|ヿ|
|Katakana_Ext|KATAKANA LETTER SMALL KU|3.2|U+31F0|ㇰ|
|Katakana_Ext|KATAKANA LETTER SMALL SI|3.2|U+31F1|ㇱ|
|Katakana_Ext|KATAKANA LETTER SMALL SU|3.2|U+31F2|ㇲ|
|Katakana_Ext|KATAKANA LETTER SMALL TO|3.2|U+31F3|ㇳ|
|Katakana_Ext|KATAKANA LETTER SMALL NU|3.2|U+31F4|ㇴ|
|Katakana_Ext|KATAKANA LETTER SMALL HA|3.2|U+31F5|ㇵ|
|Katakana_Ext|KATAKANA LETTER SMALL HI|3.2|U+31F6|ㇶ|
|Katakana_Ext|KATAKANA LETTER SMALL HU|3.2|U+31F7|ㇷ|
|Katakana_Ext|KATAKANA LETTER SMALL HE|3.2|U+31F8|ㇸ|
|Katakana_Ext|KATAKANA LETTER SMALL HO|3.2|U+31F9|ㇹ|
|Katakana_Ext|KATAKANA LETTER SMALL MU|3.2|U+31FA|ㇺ|
|Katakana_Ext|KATAKANA LETTER SMALL RA|3.2|U+31FB|ㇻ|
|Katakana_Ext|KATAKANA LETTER SMALL RI|3.2|U+31FC|ㇼ|
|Katakana_Ext|KATAKANA LETTER SMALL RU|3.2|U+31FD|ㇽ|
|Katakana_Ext|KATAKANA LETTER SMALL RE|3.2|U+31FE|ㇾ|
|Katakana_Ext|KATAKANA LETTER SMALL RO|3.2|U+31FF|ㇿ|
|Half_And_Full_Forms|FULLWIDTH EXCLAMATION MARK|1.1|U+FF01|！|
|Half_And_Full_Forms|FULLWIDTH QUOTATION MARK|1.1|U+FF02|＂|
|Half_And_Full_Forms|FULLWIDTH NUMBER SIGN|1.1|U+FF03|＃|
|Half_And_Full_Forms|FULLWIDTH DOLLAR SIGN|1.1|U+FF04|＄|
|Half_And_Full_Forms|FULLWIDTH PERCENT SIGN|1.1|U+FF05|％|
|Half_And_Full_Forms|FULLWIDTH AMPERSAND|1.1|U+FF06|＆|
|Half_And_Full_Forms|FULLWIDTH APOSTROPHE|1.1|U+FF07|＇|
|Half_And_Full_Forms|FULLWIDTH LEFT PARENTHESIS|1.1|U+FF08|（|
|Half_And_Full_Forms|FULLWIDTH RIGHT PARENTHESIS|1.1|U+FF09|）|
|Half_And_Full_Forms|FULLWIDTH ASTERISK|1.1|U+FF0A|＊|
|Half_And_Full_Forms|FULLWIDTH PLUS SIGN|1.1|U+FF0B|＋|
|Half_And_Full_Forms|FULLWIDTH COMMA|1.1|U+FF0C|，|
|Half_And_Full_Forms|FULLWIDTH HYPHEN-MINUS|1.1|U+FF0D|－|
|Half_And_Full_Forms|FULLWIDTH FULL STOP|1.1|U+FF0E|．|
|Half_And_Full_Forms|FULLWIDTH SOLIDUS|1.1|U+FF0F|／|
|Half_And_Full_Forms|FULLWIDTH DIGIT ZERO|1.1|U+FF10|０|
|Half_And_Full_Forms|FULLWIDTH DIGIT ONE|1.1|U+FF11|１|
|Half_And_Full_Forms|FULLWIDTH DIGIT TWO|1.1|U+FF12|２|
|Half_And_Full_Forms|FULLWIDTH DIGIT THREE|1.1|U+FF13|３|
|Half_And_Full_Forms|FULLWIDTH DIGIT FOUR|1.1|U+FF14|４|
|Half_And_Full_Forms|FULLWIDTH DIGIT FIVE|1.1|U+FF15|５|
|Half_And_Full_Forms|FULLWIDTH DIGIT SIX|1.1|U+FF16|６|
|Half_And_Full_Forms|FULLWIDTH DIGIT SEVEN|1.1|U+FF17|７|
|Half_And_Full_Forms|FULLWIDTH DIGIT EIGHT|1.1|U+FF18|８|
|Half_And_Full_Forms|FULLWIDTH DIGIT NINE|1.1|U+FF19|９|
|Half_And_Full_Forms|FULLWIDTH COLON|1.1|U+FF1A|：|
|Half_And_Full_Forms|FULLWIDTH SEMICOLON|1.1|U+FF1B|；|
|Half_And_Full_Forms|FULLWIDTH LESS-THAN SIGN|1.1|U+FF1C|＜|
|Half_And_Full_Forms|FULLWIDTH EQUALS SIGN|1.1|U+FF1D|＝|
|Half_And_Full_Forms|FULLWIDTH GREATER-THAN SIGN|1.1|U+FF1E|＞|
|Half_And_Full_Forms|FULLWIDTH QUESTION MARK|1.1|U+FF1F|？|
|Half_And_Full_Forms|FULLWIDTH COMMERCIAL AT|1.1|U+FF20|＠|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER A|1.1|U+FF21|Ａ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER B|1.1|U+FF22|Ｂ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER C|1.1|U+FF23|Ｃ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER D|1.1|U+FF24|Ｄ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER E|1.1|U+FF25|Ｅ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER F|1.1|U+FF26|Ｆ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER G|1.1|U+FF27|Ｇ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER H|1.1|U+FF28|Ｈ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER I|1.1|U+FF29|Ｉ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER J|1.1|U+FF2A|Ｊ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER K|1.1|U+FF2B|Ｋ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER L|1.1|U+FF2C|Ｌ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER M|1.1|U+FF2D|Ｍ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER N|1.1|U+FF2E|Ｎ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER O|1.1|U+FF2F|Ｏ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER P|1.1|U+FF30|Ｐ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER Q|1.1|U+FF31|Ｑ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER R|1.1|U+FF32|Ｒ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER S|1.1|U+FF33|Ｓ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER T|1.1|U+FF34|Ｔ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER U|1.1|U+FF35|Ｕ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER V|1.1|U+FF36|Ｖ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER W|1.1|U+FF37|Ｗ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER X|1.1|U+FF38|Ｘ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER Y|1.1|U+FF39|Ｙ|
|Half_And_Full_Forms|FULLWIDTH LATIN CAPITAL LETTER Z|1.1|U+FF3A|Ｚ|
|Half_And_Full_Forms|FULLWIDTH LEFT SQUARE BRACKET|1.1|U+FF3B|［|
|Half_And_Full_Forms|FULLWIDTH REVERSE SOLIDUS|1.1|U+FF3C|＼|
|Half_And_Full_Forms|FULLWIDTH RIGHT SQUARE BRACKET|1.1|U+FF3D|］|
|Half_And_Full_Forms|FULLWIDTH CIRCUMFLEX ACCENT|1.1|U+FF3E|＾|
|Half_And_Full_Forms|FULLWIDTH LOW LINE|1.1|U+FF3F|＿|
|Half_And_Full_Forms|FULLWIDTH GRAVE ACCENT|1.1|U+FF40|｀|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER A|1.1|U+FF41|ａ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER B|1.1|U+FF42|ｂ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER C|1.1|U+FF43|ｃ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER D|1.1|U+FF44|ｄ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER E|1.1|U+FF45|ｅ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER F|1.1|U+FF46|ｆ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER G|1.1|U+FF47|ｇ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER H|1.1|U+FF48|ｈ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER I|1.1|U+FF49|ｉ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER J|1.1|U+FF4A|ｊ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER K|1.1|U+FF4B|ｋ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER L|1.1|U+FF4C|ｌ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER M|1.1|U+FF4D|ｍ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER N|1.1|U+FF4E|ｎ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER O|1.1|U+FF4F|ｏ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER P|1.1|U+FF50|ｐ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER Q|1.1|U+FF51|ｑ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER R|1.1|U+FF52|ｒ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER S|1.1|U+FF53|ｓ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER T|1.1|U+FF54|ｔ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER U|1.1|U+FF55|ｕ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER V|1.1|U+FF56|ｖ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER W|1.1|U+FF57|ｗ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER X|1.1|U+FF58|ｘ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER Y|1.1|U+FF59|ｙ|
|Half_And_Full_Forms|FULLWIDTH LATIN SMALL LETTER Z|1.1|U+FF5A|ｚ|
|Half_And_Full_Forms|FULLWIDTH LEFT CURLY BRACKET|1.1|U+FF5B|｛|
|Half_And_Full_Forms|FULLWIDTH VERTICAL LINE|1.1|U+FF5C|｜|
|Half_And_Full_Forms|FULLWIDTH RIGHT CURLY BRACKET|1.1|U+FF5D|｝|
|Half_And_Full_Forms|FULLWIDTH TILDE|1.1|U+FF5E|～|
|Half_And_Full_Forms|HALFWIDTH IDEOGRAPHIC FULL STOP|1.1|U+FF61|｡|
|Half_And_Full_Forms|HALFWIDTH LEFT CORNER BRACKET|1.1|U+FF62|｢|
|Half_And_Full_Forms|HALFWIDTH RIGHT CORNER BRACKET|1.1|U+FF63|｣|
|Half_And_Full_Forms|HALFWIDTH IDEOGRAPHIC COMMA|1.1|U+FF64|､|
|Half_And_Full_Forms|HALFWIDTH KATAKANA MIDDLE DOT|1.1|U+FF65|･|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER WO|1.1|U+FF66|ｦ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SMALL A|1.1|U+FF67|ｧ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SMALL I|1.1|U+FF68|ｨ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SMALL U|1.1|U+FF69|ｩ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SMALL E|1.1|U+FF6A|ｪ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SMALL O|1.1|U+FF6B|ｫ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SMALL YA|1.1|U+FF6C|ｬ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SMALL YU|1.1|U+FF6D|ｭ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SMALL YO|1.1|U+FF6E|ｮ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SMALL TU|1.1|U+FF6F|ｯ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK|1.1|U+FF70|ｰ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER A|1.1|U+FF71|ｱ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER I|1.1|U+FF72|ｲ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER U|1.1|U+FF73|ｳ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER E|1.1|U+FF74|ｴ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER O|1.1|U+FF75|ｵ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER KA|1.1|U+FF76|ｶ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER KI|1.1|U+FF77|ｷ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER KU|1.1|U+FF78|ｸ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER KE|1.1|U+FF79|ｹ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER KO|1.1|U+FF7A|ｺ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SA|1.1|U+FF7B|ｻ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SI|1.1|U+FF7C|ｼ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SU|1.1|U+FF7D|ｽ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SE|1.1|U+FF7E|ｾ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER SO|1.1|U+FF7F|ｿ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER TA|1.1|U+FF80|ﾀ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER TI|1.1|U+FF81|ﾁ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER TU|1.1|U+FF82|ﾂ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER TE|1.1|U+FF83|ﾃ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER TO|1.1|U+FF84|ﾄ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER NA|1.1|U+FF85|ﾅ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER NI|1.1|U+FF86|ﾆ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER NU|1.1|U+FF87|ﾇ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER NE|1.1|U+FF88|ﾈ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER NO|1.1|U+FF89|ﾉ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER HA|1.1|U+FF8A|ﾊ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER HI|1.1|U+FF8B|ﾋ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER HU|1.1|U+FF8C|ﾌ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER HE|1.1|U+FF8D|ﾍ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER HO|1.1|U+FF8E|ﾎ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER MA|1.1|U+FF8F|ﾏ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER MI|1.1|U+FF90|ﾐ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER MU|1.1|U+FF91|ﾑ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER ME|1.1|U+FF92|ﾒ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER MO|1.1|U+FF93|ﾓ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER YA|1.1|U+FF94|ﾔ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER YU|1.1|U+FF95|ﾕ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER YO|1.1|U+FF96|ﾖ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER RA|1.1|U+FF97|ﾗ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER RI|1.1|U+FF98|ﾘ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER RU|1.1|U+FF99|ﾙ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER RE|1.1|U+FF9A|ﾚ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER RO|1.1|U+FF9B|ﾛ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER WA|1.1|U+FF9C|ﾜ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA LETTER N|1.1|U+FF9D|ﾝ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA VOICED SOUND MARK|1.1|U+FF9E|ﾞ|
|Half_And_Full_Forms|HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK|1.1|U+FF9F|ﾟ|

</div></details>

# おわりに

Unicode Normalization Form って使いづらいなぁと思って、ネットで調べてみても、この仕様に否定的な記事はあまり見つかりませんでした。

みなさん、norm や width をそのまま使ってるんですかね？ 
それとも、特に難しくもないからみんな独自にライブラリを作ってるのかな？
あ、そもそもノーマライズとかしてないんでしょうか？

それが気になってます。誰か教えてください（笑）

また、Golang を触り始めてからまだ日が浅いので、ライブラリの実装に抜けがあるかもしれません。

* Go らしくない部分
* 非効率な箇所
* バグ

などを見つけたら教えてもらえると嬉しいです。

それでは！