Unicode の正準等価性と、NFC ⇄ NFD の相互変換
Unicode には正準等価性という概念がある。これは「この文字とこの文字は、見た目も機能も同じですよ」という意味。例えば、ガ (U+30AC) と ガ (U+30AB U+3099) は、データとしては異なる文字列ではあるけれど、見た目も機能も同じ。このとき「これらは互いに正準等価だ」という。
💡 以降では、便宜的に ガ (U+30AB U+3099) を カ◌゙ のように表します。ガ (U+30AB U+3099) は ガ (U+30AC) と正準等価で、見た目で区別できないので😅
正準等価な文字について、合成された姿を NFC (正規化形式 C。合成 Composition の C) と呼び、分解された姿を NFD (正規化形式 D。分解 Decomposition の D) と呼ぶ。例えば、ガ (U+30AC) は NFC であり、カ◌゙ (U+30AB U+3099) は NFD だね。
フォントに NFC が未収録なら、NFD にフォールバック
僕の自作フォント OCR-BK は、ガ (U+30AC) のグリフを収録していない。OCR-BK は、JIS 規格 OCR-K (JIS X 9003) をデジタルフォント化したもので、JIS 規格の OCR-K には ガ のグリフが含まれない。そのため、僕の OCR-BK でも ガ (U+30AC) を収録しなかった。
にもかかわらず、OCR-BK で ガ (U+30AC) を入力しても、問題なく表示される。例えば以下の画像は Inkscape 1.4.2 で入力した様子。おそらく ガ (U+30AC) のグリフが無いので、代わりに正準等価な NFD である カ◌゙ (U+30AB U+3099) にフォールバックして表示してくれてるのだと思う。すごい!
カ゛ (U+30AB U+309B)、[下] ガ (U+30AC) → カ◌゙ (U+30AB U+3099) に分解🤔 カ◌゙ (U+30AB U+3099) を描画する際には、おそらく合成という処理が実行されてる。合成では、GPOS テーブルの mark 機能を使用する… と思う。全然違うかもしれない笑。
NFD フォールバックを効かせるために、OCR-BK は結合文字の濁点 ◌゙ (U+3099)と半濁点 ◌゚ (U+309A) を収録してる。これらは普通の濁点記号 ゛ (U+309B) と半濁点記号 ゜ (U+309C) とは別の文字。ちなみに OCR-BK は、゛ (U+309B) と ゜ (U+309C) も収録してるよ。
NFD フォールバック描画は、ソフト側の機能?
NDF によるフォールバック描画は、Unicode の機能と言うより、ソフト側の機能なのかなと推測している。というのも、v 1.1 頃の Inkscape では OCR-BK の濁点の文字が表示できなかったのだ。なので、以前は清音 カ (U+30AB)と濁点記号 ゛ (U+309B) をわざわざ別で入力していたのよね😅
Inkscape で NFD フォールバックを搭載してくれた開発者に感謝 🎉 とても便利で嬉しい!厳密に Inkscape 1.4.2 から NFD フォールバック描画に対応したのかは不明。もう少し前の版から対応していたかもね。
(余談 1) フォールバック描画を前提に設計された巧妙なフォント
Droid Sans Fallback というフォントは、16,000+ の漢字と 11,000+ のハングルを収録しながら、ファイルサイズはわずか 3 MB と驚くほど軽量だそうだ。その秘密は、グリフのフォールバック描画。
全体で3万字弱もの文字を収録しているのに、ファイルサイズはたったの3MB。(…) それを可能にしているのが、分合活字的なグリフの組み立て方。
上の画像は、ためしに「蔒」という漢字を FontForge で開いてみたところ。glyph31874, glyph42395, glyph32087 と小さく表示されている。実はこのグリフ、 アウトラインの情報はまったく入っていなくて、「glyph31874 がこの位置、glyph42395 がここ、で glyph32087 がこっち」という感じになっている。つまり、実際にアウトラインの情報が入っているのは glyph31874, glyph42395, glyph32087 の方であって、「蔒」ではそれらを呼び出すだけになっている。
16000字超の漢字と11000字超のハングルが入った軽量CJKフォント Droid Sans Fallback – しろもじメモランダム
OCR-BK と Droid Sans Fallback のフォールバックの仕組みは、ちょっと違う気がする。OCR-BK は、 Unicode に組み込みの正準等価性の情報を利用して、コードポイントの与えられた文字同士を合成する。上述の通り、これが動作するにはソフト側の対応も必要になる (と思う)。
一方で Droid Sans Fallback は、それぞれのグリフが分解情報を独自に持っている (そのようなデータは Unicode の標準には無いと思う)。そして、部品たちはコードポイントを持たない独自の拡張領域に保存されているそうだ。これが動作するために、ソフト側の対応が必要かは不明。要らないかも?
💡 IDS (Ideographic Description Sequence、漢字構成記述文字列) という仕組みがある。これは部品を組み合わせて漢字を表現する方法。漢字の組み立て情報が Unicode に組み込みで標準化されれば、Droid Sans Fallback はもっと軽量になるかもね。
(余談 2) 正規化形式と、その種類: D、C、KD、KC
正規化形式には D、C、KD、KC の 4 種がある。D は「正準等価性による分解」、C は「正準等価性による分解と合成」。
| 略号 | 名称 (EN) | 名称 (JA) | 説明 |
|---|---|---|---|
| NFD | Normalization Form Canonical Decomposition | 正規化形式 D | 文字は正準等価性によって分解される。 |
| NFC | Normalization Form Canonical Composition | 正規化形式 C | 文字は正準等価性によって分解され、再度合成される。結果として文字の並びが変換前と変わることもありうる。 |
| NFKD | Normalization Form Compatibility Decomposition | 正規化形式 KD | 文字は互換等価性によって分解される。 |
| NFKC | Normalization Form Compatibility Composition | 正規化形式 KC | 文字は互換等価性によって分解され、正準等価性によって再度合成される。 |
KD は「互換等価性による分解」、KC は「互換等価性による分解と、正準等価性による合成」。互換等価とは「見た目や機能は一致しないけど、大雑把な意味は同じ」みたいな意味。例えば、ガ を NFKD 化すると カ◌゙ となり、組文字の ㌀ を NFKC 化すると アパート となる。
🤔 互換等価性に基づいた変換は、一方通行。例えば、カ◌゙ を NFKD しても ガ に変換されないし、アパート を NFKC しても ㌀ に変換されない (と思う)。

