【Python】フォントファイルに含まれる文字の一覧を出力する

フォントファイル(*.ttf)から文字のコードポイント(U+????)の一覧を出力する方法を紹介します。
使用する言語はPythonです。

# fonttoolsのインストール
フォントファイルの処理にfonttoolsを使用します。pipなどを使ってインストールしてください。
```
pip install fonttools
```
virtualenvやgitを使った方法は以下にあります。

https://pypi.org/project/fonttools/

# フォントファイルの準備

この記事では、サンプルのフォントファイルとして __ipamjm.ttf（IPAmj明朝フォント）__ を使用します。
窓の杜などから事前にダウンロードして作業用のフォルダに置いてください。

IPAmj明朝フォント
https://forest.watch.impress.co.jp/library/software/ipamjfont/

ちなみに、Windowsの場合は、

C:\Windows\Fonts

に各種フォントファイルが置いてあります。
このフォルダをエクスプローラーで開くとフォントの一覧が表示され、右クリックからコピーできます。
ファイルの一覧を直接見たい場合は、コマンドプロンプトなどからCUIでアクセスします。
```
dir "C:\Windows\Fonts"
```
### 拡張子が*.ttcだった場合
拡張子*.ttcはTrueType Collectionファイルで、複数の*.ttfファイルを含んだフォントファイルです。
処理の前に個別の*.ttfファイルへ変換する必要がありますので、以下のページを参考に変換を行ってください。

https://qiita.com/maboy/items/9f64d406c830aab548b1

https://butami-study.com/python/51/

# フォントファイルの詳細な情報を取得

こちらのページにfonttoolsを使って個別のフォントファイルから詳細な情報を*.json形式で出力するサンプルコードがあります。
出力結果の例もありますので参考にしてください。

https://docsaid.org/ja/blog/extract-font-info-by-python/

ただし、サンプルコードを実行する場合、出力に日本語を含んでいるとエラーとなるため、json.dump()関数の中にあるopen()関数を以下のように書き換えてください。
```
open('OcrB-Regular-Info.json', 'w')
```
↓
```
open('OcrB-Regular-Info.json', 'w',encoding="utf8")
```

# テーブルデータ
フォントファイルはフォントについての各情報をテーブルデータという状態で保持しています。

https://azelpg.gitlab.io/azsky2/note/prog/opentype/02_struct.html#ct3

このうち、nameテーブルとcmapテーブルの出力方法を紹介します。

# nameテーブル
フォント名などの文字列の情報はnameテーブルに格納されています。
以下のコードで一覧出力することができます。

```python:sample1.py
from fontTools.ttLib import TTFont

font = TTFont("ipamjm.ttf")
print("platformID,platEncID,langID,nameID,name")
for name in font['name'].names:
    print("{0},{1},0x{2:04x},{3},{4}".format(name.platformID,name.platEncID,name.langID,name.nameID,name))
```
実行結果
```
platformID,platEncID,langID,nameID,name
1,0,0x0000,0,Copyright(c) Information-technology Promotion Agency, Japan (IPA), 2011-2019. You must accept "https://opensource.org/licenses/IPA/" to use this product.
1,0,0x0000,1,IPAmjMincho
1,0,0x0000,2,Regular
1,0,0x0000,3,IPAmjMincho Version 006.01
1,0,0x0000,4,IPAmjMincho
1,0,0x0000,5,Version 006.01
1,0,0x0000,6,IPAmjMincho
1,0,0x0000,13,https://opensource.org/licenses/IPA/
1,0,0x0000,14,https://opensource.org/licenses/IPA/
1,1,0x000b,0,Copyright(c) Information-technology Promotion Agency, Japan (IPA), 2011-2019. You must accept "https://opensource.org/licenses/IPA/" to use this product.
1,1,0x000b,1,IPAmj明朝
1,1,0x000b,2,Regular
1,1,0x000b,3,IPAmjMincho Version 006.01
1,1,0x000b,4,IPAmj明朝
1,1,0x000b,5,Version 006.01
1,1,0x000b,6,IPAmjMincho
1,1,0x000b,13,https://opensource.org/licenses/IPA/
1,1,0x000b,14,https://opensource.org/licenses/IPA/
3,1,0x0409,0,Copyright(c) Information-technology Promotion Agency, Japan (IPA), 2011-2019. You must accept "https://opensource.org/licenses/IPA/" to use this product.
3,1,0x0409,1,IPAmjMincho
3,1,0x0409,2,Regular
3,1,0x0409,3,IPAmjMincho Version 006.01
3,1,0x0409,4,IPAmjMincho
3,1,0x0409,5,Version 006.01
3,1,0x0409,6,IPAmjMincho
3,1,0x0409,13,https://opensource.org/licenses/IPA/
3,1,0x0409,14,https://opensource.org/licenses/IPA/
3,1,0x0411,0,Copyright(c) Information-technology Promotion Agency, Japan (IPA), 2011-2019. You must accept "https://opensource.org/licenses/IPA/" to use this product.
3,1,0x0411,1,IPAmj明朝
3,1,0x0411,2,Regular
3,1,0x0411,3,IPAmjMincho Version 006.01
3,1,0x0411,4,IPAmj明朝
3,1,0x0411,5,Version 006.01
3,1,0x0411,6,IPAmjMincho
3,1,0x0411,13,https://opensource.org/licenses/IPA/
3,1,0x0411,14,https://opensource.org/licenses/IPA/
```
各IDの意味は以下のページを参照してください。

https://azelpg.gitlab.io/azsky2/note/prog/opentype/05_name.html

各IDがわかっていれば、文字列の情報を直接参照することもできます。
例えば、Windows(platformID=3,platEncID=1)の日本語(langID=0x0411)のフォントファミリー名(nameID=1)を取得する場合は、以下のようになります。

```python:sample2.py
from fontTools.ttLib import TTFont

font = TTFont("ipamjm.ttf")
print(font['name'].getName(1,3,1,0x0411))
```
実行結果
```
IPAmj明朝
```
getName()関数の詳細は以下のページを参照してください。

https://fonttools.readthedocs.io/en/latest/ttLib/tables/_n_a_m_e.html

# cmapテーブル
cmapテーブル、フォントファイルに含まれる文字のコードポイントとフォントのグリフ（形）の対応関係を格納したテーブルになります。ただし、実際の文字の形のデータはglyphテーブルに格納されており、cmapテーブルにはそれを参照するためのインデックスが格納されています。
cmapテーブルには複数のサブテーブルが含まれており、環境ごとに別々のリストが格納されいます。
以下のコードでサブテーブルを一覧出力することができます。

```python:sample3.py
from fontTools.ttLib import TTFont

font = TTFont("ipamjm.ttf")
print("format,platformID,platEncID")
for cmap in font['cmap'].tables:
    print("{0},{1},{2}".format(cmap.format,cmap.platformID,cmap.platEncID))
```
実行結果
```
format,platformID,platEncID
4,0,3
14,0,5
4,3,1
12,3,10
```
各IDの意味は以下のページを参照してください。

https://azelpg.gitlab.io/azsky2/note/prog/opentype/08_cmap.html


このうち一番下のfomrmat=12,platformID=3,platEncID=10のデータが全体のデータを含んでいるサブテーブルになります。
これに含まれているリストを参照するには以下のコードを実行します。

```python:sample4.py
from fontTools.ttLib import TTFont

font = TTFont("ipamjm.ttf")
#platformIDとplatEncIDを指定
cmap = font['cmap'].getcmap(3,10)
print("code,glyph")
for code, glyph in cmap.cmap.items():
    print("U+{0:X},{1}".format(code,glyph))
```
実行結果（一部）
```
U+53CA,mj007990
U+53CB,mj007992
U+53CC,mj007993
U+53CD,mj007994
U+53CE,mj007996
U+53CF,mj007997
U+53D0,mj007998
U+53D2,mj007999
U+53D3,mj008000
U+53D4,mj008002
U+53D5,mj008003
U+53D6,mj008004
U+53D7,mj008005
U+53D9,mj008007
U+53DA,mj008008
U+53DB,mj008010
U+53DC,mj008013
U+53DD,mj008014
U+53DE,mj008016
U+53DF,mj008018
U+53E0,mj008020
U+53E1,mj008021
U+53E2,mj008022
U+53E3,mj008023
```
[コードポイント],[glyphインデックス]の順に並んでいます。

また、getBestCmap()関数を使用すると、自動的に最適なサブテーブルを選んで取得することもできます。
IDを指定する必要がないため、こちらの方がシンプルです。

```python:sample5.py
from fontTools.ttLib import TTFont

font = TTFont("ipamjm.ttf")
cmap = font['cmap'].getBestCmap()
print("code,glyph")
for code, glyph in cmap.items():
    print("U+{0:X},{1}".format(code,glyph))
```
getBestCmap()関数の詳細は以下のページを参照してください。

https://fonttools.readthedocs.io/en/stable/ttLib/tables/_c_m_a_p.html

# 異体字セレクタ
最近の日本語フォントファイルの中にはcmapテーブルのサブテーブルとして異体字の情報含んでいるものがあります。
異体字の情報は元の文字に対応するコードポイント（異体字セレクタ）を持っています。

https://ja.wikipedia.org/wiki/%E7%95%B0%E4%BD%93%E5%AD%97%E3%82%BB%E3%83%AC%E3%82%AF%E3%82%BF

異体字セレクタを含むサブテーブルは、fomrmat=14のものです。
以下のコードで異体字セレクタの一覧を出力できます。
```python:sample6.py
from fontTools.ttLib import TTFont

font = TTFont("ipamjm.ttf")

for cmap in font['cmap'].tables:
    if cmap.format==14:
        print("code,uvs,glyph")
        for uvs in cmap.uvsDict:
            for code,glyph in cmap.uvsDict[uvs]:
                print("U+{0:X},U+{1:X},{2}".format(code,uvs,glyph))
```
実行結果（一部）
```
U+83DF,U+E0107,mj022121
U+845B,U+E0107,mj022337
U+8612,U+E0107,mj030221
U+9038,U+E0107,mj030303
U+9052,U+E0107,mj026054
U+9055,U+E0107,mj026065
U+9077,U+E0107,mj026143
U+9083,U+E0107,mj026175
U+9760,U+E0107,mj027926
U+97FF,U+E0107,mj028144
U+990C,U+E0107,mj028413
U+9957,U+E0107,mj028568
U+9F08,U+E0107,mj029970
U+9F8D,U+E0107,mj030124
U+FA24,U+E0107,mj030228
U+908A,U+E0108,None
U+20525,U+E0108,None
U+51DE,U+E0108,mj030203
U+53A9,U+E0108,mj007951
U+6168,U+E0108,mj030258
```
[元の文字のコードポイント],[異体字セレクタのコードポイント],[glyphインデックス]の順に並んでいます。
一部、glyphインデックスが"None"となっているのは、異体字セレクタの割り当てはあってもglyphの情報を持っていない文字と思われます。

# ファイルへ出力
以下のコードで異体字セレクタも含めたコードポイントの一覧をCSVファイルへ出力できます。
"cmap.csv"が出力されます。
```python:sample6.py
from fontTools.ttLib import TTFont

font = TTFont("ipamjm.ttf")

with open("cmap.csv",mode='w') as fp:
    cmap = font['cmap'].getBestCmap()
    fp.write("code,uvs,glyph\n")
    for code, glyph in cmap.items():
        fp.write("U+{0:X},,{1}\n".format(code,glyph))

    for cmap in font['cmap'].tables:
        if cmap.format==14:
            for uvs in cmap.uvsDict:
                for code,glyph in cmap.uvsDict[uvs]:
                    fp.write("U+{0:X},U+{1:X},{2}\n".format(code,uvs,glyph))
```

# その他
出力されたコードポイントの一覧の中には文字として表示されない制御コードやスペースなども含んでいます。表示される「文字」だけを抽出するには、この後に追加の処理が必要になります。
こちらのページのremove_control_characters()関数が参考になりそうです。

https://docsaid.org/ja/blog/extract-font-info-by-python/

glyphインデックスからglyph（文字の形）のデータを取得する方法については、以下のページを参照してください。

https://qiita.com/scrpgil/items/7c7c0a354b3688ddfc6b

https://memo.eightban.com/font/font-svg