サイトアイコン 回れ右の内輪差

Unicode の NFD (正規化形式 D) と、フォントのフォールバック描画

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 の収録グリフ (一部)

にもかかわらず、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) をわざわざ別で入力していたのよね😅

OCR-BK で濁点を含む字を入力すると、かつて Inkscape では正しく表示できなかった

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)説明
NFDNormalization Form Canonical Decomposition正規化形式 D文字は正準等価性によって分解される。
NFCNormalization Form Canonical Composition正規化形式 C文字は正準等価性によって分解され、再度合成される。結果として文字の並びが変換前と変わることもありうる。
NFKDNormalization Form Compatibility Decomposition正規化形式 KD文字は互換等価性によって分解される。
NFKCNormalization Form Compatibility Composition正規化形式 KC文字は互換等価性によって分解され、正準等価性によって再度合成される。
Unicode正規化 – Wikipedia

KD は「互換等価性による分解」、KC は「互換等価性による分解と、正準等価性による合成」。互換等価とは「見た目や機能は一致しないけど、大雑把な意味は同じ」みたいな意味。例えば、ガ を NFKD 化すると カ◌゙ となり、組文字の を NFKC 化すると アパート となる。

🤔 互換等価性に基づいた変換は、一方通行。例えば、カ◌゙ を NFKD しても ガ に変換されないし、アパート を NFKC しても に変換されない (と思う)。

参考文献

モバイルバージョンを終了