フォント合成の問題点

フォント合成の問題点
前回説明したフォント合成は、「フォントの統合」機能を使って、かなフォントに日本語フォントを合成するというようなやり方でした。

この場合、作業は比較的楽ですが、細かく見ると、以下のような問題点があります。

  • CID フォントのままで合成する場合、いくつか条件が課される。
  • 使われない無駄なグリフが残る場合がある。
  • EM 値などが異なる場合、あらかじめグリフの調整が必要。
  • CID フォントを単一化した場合、一部 Unicode が重複するグリフがあるため、本来のグリフとは異なるものが、その Unicode のグリフとしてセットされる場合がある。

以下、これらの問題について説明していきます。
無駄なグリフが残る可能性
まず、合成元と合成するフォントで、以下のグリフがあるものとします。

合成元 = 開いているフォント
合成するフォント = ファイルの統合で選択したフォント


合成元 (A)合成するフォント (B)
文字0 (U+0030)
[uni0030.hw]
0 (U+0030)
[uni0030.hw]
GSUB 置換vrt2 [Japan1.247.vert]
pwid [zero]
fwid [uniFF10]
vrt2 [Japan1.247.vert]
zero [uni00D8.hw]
twid [Japan1.9758]
...
pwid [zero]
fwid [uniFF10]

フォントを統合した時、2つのフォントでグリフが重複しているかどうかは、グリフ名によって判別されます。

グリフ名が同じものは、同じグリフとして扱われます。
仮にグリフの Unicode 値の指定が同じでも、グリフ名が異なる場合は、別のグリフとして扱われます。

これにより、上記の2つのグリフでは、グリフ名が一致しているので、統合時には重複するグリフとして扱われます。
つまり、B のグリフは統合されず、A のグリフが残ることになります。

この時に問題となるのは、GSUB 置換で指定されているグリフです。
置換グリフの問題
B のフォント内には、縦書き用などの置き換えグリフも含まれているため、A のフォントのグリフ名と重複しない限りは、それらのグリフも統合されます。

しかし、統合時に、置換先の元となるグリフが統合されなかった場合 (この場合、B の uni0030.hw)、その置換元に設定されていた GSUB 置換情報もセットされないということになるので、A のグリフでは置換情報のないグリフが残り、また、A のグリフの置換情報によって参照がされない B の置換先グリフが統合されてしまうことになります。

上記のグリフの場合、Japan1.247.vert, zero, uniFF10 の置換先グリフは両方で共通しているので問題ありませんが、B のグリフでしか設定されていない uni00D8.hw, Japan1.9758 などは、B のグリフが統合時に弾かれることによって、置換情報が設定されません。

しかし、uni00D8.hw, Japan1.9758 のグリフ自体はフォント内に統合されます。

これらのグリフが、他のグリフから置換先として参照されておらず、また有効なエンコーディングコード (Unicode など) が割り当てられていない場合は、GSUB 置換情報がないことにより、文字として参照する方法がないため、全く使われない無駄なグリフを格納してしまうことになります。

フォントとして使う分には特に問題はありませんが、無駄なグリフを残してしまうのは出来るだけ避けたいところです。

しかし、手動でこれらの対策を行うのはかなり大変なので、実際には、スクリプトを使って自動的に行うことになります。
CID フォントの問題点
CID フォントについて
CID フォントを開いてグリフを見てみると、同じ形のグリフが複数存在する場合があると思います。

CID では、半角幅・プロポーショナル・縦書き用・縦書き時の 90 度回転用といったように、各文字の各用途ごとに、それぞれ一つの CID が割り当てられています。

例えば、半角 "A" のプロポーショナル用は [34]、半角固定幅は [264]、90 度回転した斜体は [12993] といった値が割り当てられています。

Adobe-Japan1 の CID 定義に関しては、以下の URL をご覧ください。
https://github.com/adobe-type-tools/Adobe-Japan1/

OpenType (CID) のかなフォントを開くと、必要のない漢字などにもグリフが定義されている場合があり、なぜなのかと疑問に思うと思いますが、CID フォントで Adobe-Japan1-* と指定されていた場合、その文字コレクションで定義されているすべてのグリフをフォント内に含める必要があります。

例えば、Adobe-Japan1-3 なら 9354個、Adobe-Japan1-6 なら 2万3058個のグリフが定義されているので、フォント内に格納されているグリフ数は、その数と一致しなければなりません。

そのため、CID (Adobe-Japan1-*) のフォントである場合は、使われないグリフに対しても、形状がセットされている場合があります。
Adobe-Identity-0
CID フォントで、明示的に定義された文字コレクションを使う場合は、その定義に沿ってグリフを割り当てていきますが、各 CID に対して、好きなようにグリフを割り当てたいという場合は、Adobe-Identity-0 を使います。

これは、CID に対して明確に定義されたグリフはなく、そのフォント内で自由にグリフが割り当てられている、ということを示します。
(Unicode との関連付けは、すべてのフォントで、cmap テーブルにて定義されるので、Unicode からの参照については問題ありません)

Adobe-Japan1 で定義されていないグリフを大量に格納する場合や、不要なグリフを省きたいという場合に使います。

源ノ角ゴシックや、源ノ明朝は、CJK フォントであるために、大量のグリフを一つのフォントに含められるようにするため、この Adobe-Identity-0 が使われています。
外字
CID フォントでは、各文字コレクションによって、グリフ数が決まっているので、基本的にグリフを増やしたり減らしたりはできません。

では、外字や、CID で定義されていないグリフを含めたい時はどこにグリフを定義するかと言うと、CID の一部に、Unicode の私用領域にあたる範囲があるので、そこを使います。
(Adobe-Japan1-6 以降)

CID 20473〜20522DingbatsU+EF00〜U+EF31
CID 20553〜20586DingbatsU+F0A7〜U+F0CA

ただし、FontForge で開いた場合、Unicode 値は指定されていないので、Unicode の私用領域に割り当てたい場合は、グリフ情報で Unicode 値を指定してください。
Unicode 重複問題
FontForge で CID フォントを開いた場合、一部の文字で Unicode 値が重複する場合があります。
これらのグリフは、CID フォントを単一化した時に問題が出ます。

スクリプト
以下、CID フォント内で Unicode が重複しているグリフをチェックする Python3 スクリプトを作りました。
コマンドライン上で、引数に、チェックするフォントファイル名を指定して、実行してください。

$ python3 checkunicode.py font.otf

checkunicode.py
import fontforge
import sys

if len(sys.argv) < 2:
    print('checkunicode.py [CID-FONTNAME]')
    sys.exit(0)

font = fontforge.open(sys.argv[1])

if not font.is_cid:
    print('[!] font is not CID')
    sys.exit(0)

gmap = {}
for subfont in range(font.cidsubfontcnt):
    font.cidsubfont = subfont

    for gname in font:
        g = font[gname]
        if g.unicode == -1: continue

        # 異体字の場合は除外
        if g.altuni:
            flag = False
            for t in g.altuni:
                if t[1] != -1:
                    flag = True
                    break
            if flag: continue
        
        if g.unicode in gmap:
            gmap[g.unicode].append(g.glyphname)
        else:
            gmap[g.unicode] = [g.glyphname]

for uni,names in gmap.items():
    if len(names) > 1:
        print('U+{0:04X} ('.format(uni) + chr(uni) + ')')
        for n in names:
            print(' => ' + n)

font.close()

スクリプトは FontForge GUI 上でも実行できますが (上記の場合はそのままでは動きません)、GUI 上では、font.cidsubfont で CID のサブフォントを切り替える時に、現バージョンにおいてはほぼ 100% の確率で強制終了するので、サブフォント切り替えの処理を行う場合は、GUI 上では使い物になりません。

Unicode が重複するグリフ
上記のスクリプトを使ってチェックしてみると、例えば、以下のような結果が出て、Unicode が重複しているグリフのグリフ名が表示されます。

基本的には、Proportional と Dingbats で重複する場合が多いです。

U+201C (“)
 => uni201C.dup2
 => quotedblleft
U+201D (”)
 => uni201D.dup2
 => quotedblright

この場合、単一化後に「フォントの統合」で合成すると、uni201C.dup2 のグリフが U+201C に割り当てられ、quotedblleft は Unicode がない状態で格納されます。
おそらく、uni201C.dup2 の方がエンコーディング順で上に来るためです。

しかし、U+201C の正式なグリフ名は quotedblleft なので、ここでは、quotedblleft が使われるべきです。

対策
手動で対策を行うのであれば、Unicode が重複しているグリフの Unicode 値を、グリフ情報から編集します。
Unicode 値に -1 を指定すれば、Unicode がない状態となります。

また、「Alternate Unicode Encodings / Variation Selectors」に「U+***|U+0000」という形で、代替の Unicode 値が設定されている場合は、それも削除してください。
Variation Selector の値が 0 でない場合は、異体字の指定であるため、そのままにしてください。
波ダッシュ問題
生成したフォントで「」が表示されない場合、U+301C と U+FF5E のグリフが正しく設定されていない可能性があります。

「〜 (波ダッシュ)」は、正式には「U+301C (WAVE DASH)」として定義されていますが、Windows (日本語) の場合、Shift-JIS の 0x8160 を「U+FF5E (FULLWIDTH TILDE)」へと変換するため、Linux と Windows で、波ダッシュの Unicode 値が異なる値として参照されます。

そのため、フォント内で U+FF5E の波ダッシュしか指定されていない場合、Linux 上でフォントを使うと、波ダッシュが表示されなかったりします。

このため、フォント内では、U+301C と U+FF5E の両方で、波ダッシュのグリフを指定する必要があります。
同じグリフを使う方法
FontForge 上で、一つのグリフに複数の Unicode 値を指定して、各 Unicode で同じグリフを使いたい場合は、代替 Unicode を指定します。

グリフ情報を開くと、そこで、メインの Unicode 値を一つ指定できますが、
Alternate Unicode Encodings / Variation Selectors」のリストに代替の Unicode 値を登録することで、複数の Unicode で同じグリフを使用することができます。

リストの New の部分をクリックすると、項目を一つ追加できます。

「Unicode」と「Variation Selector」にそれぞれ「U+0000」が指定されているので、左の Unicode の方をクリックして、Unicode 値を入力してください。

「Variation Selector」が U+0000 の場合、指定された Unicode を代替 Unicode として扱う、ということを示します。
修正
フォントによっては、波ダッシュのグリフ名が「uni301C」、Unicode 値が「U+FF5E」、代替 Unicode が「U+FF5E/U+0000」になっている場合があります。

この場合は、メインの方のUnicode 値を「U+301C」に修正してください。