TTFの全グリフをPNGにしてみる

以前、別記事にてアスキーアート関連の論文をまとめました
[アスキーアート関連の論文を探してみた](https://qiita.com/xiaca/items/544ad6c0580b3027aeda)

その中で「アスキーアート分類手法の比較検討」にて「文字単位で画像特徴量(HOG)を抽出し...」というのをやってみたくて色々模索した記事です。この記事ではttfファイルをpngで出力するところまでやります。最終的な目標は機械学習によるAAの分類です。

本記事で紹介する方法は以下の手順を踏んでttfをpngに変換します。

1. fonttoolsを使ってttfファイルのグリフ一覧を読み込み
2. SVGPathPenを使って全グリフをSVGに変換する
3. cairosvgを使ってSVGをPNGに変換する
4. PNGをリサイズして縮小する※必要ないかも...

興味ある方は以下、手順を記載するので読み進めてください。なお、1〜2の内容は以下のブログを参考にさせていただいています。めちゃくちゃ詳しく書いてあるのでfonttools知らない方は是非とも読んでみてください。
[fontTools のペンを使ってグリフのアウトラインを取得する](https://shiromoji.hatenablog.jp/entry/2017/11/26/221902)

※以下の手順はjupyter notebookを利用することを想定しています。

## 1. fonttoolsを使ってttfファイルのグリフ一覧を読み込み
まず、Pythonでttfファイルを扱うためにfonttoolsを使用します。インストール方法はpipから

```bash
pip install fonttools
```

fonttoolsでフォントを読み込んでグリフを取得するために以下の処理を行います。

```python
from fontTools.ttLib import TTFont
font = TTFont('/Users/eskey/workspace/aahub/ml/Saitamaar.ttf')
glyph_set = font.getGlyphSet() 
cmap = font.getBestCmap() 
```

glyph_setがグリフオブジェクト、cmapがUnicodeとグリフの対応テーブルになります。


## 2. SVGPathPenを使って全グリフをSVGに変換する
グリフ情報の準備が済んだのでSVGに変換します。まずは、ファイル出力用にディレクトリを作成します。

```python
import os
os.mkdir("output_svg")
os.mkdir("output_png")
os.mkdir("output_resize")
```

次に、全グリフをSVGに変換する関数を記述します。

```python
from textwrap import dedent
from fontTools.pens.svgPathPen import SVGPathPen

# GlyphをSVGに変換
def save_all_glyph_as_svg(font):    
    glyph_set = font.getGlyphSet()
    cmap = font.getBestCmap()
    
    output_path = "output_svg/"
    for c in cmap:
        glyph_name = cmap[c]
        glyph = glyph_set[glyph_name]
        svg_path_pen = SVGPathPen(glyph_set)
        glyph.draw(svg_path_pen)

        ascender = font['OS/2'].sTypoAscender
        descender = font['OS/2'].sTypoDescender
        width = glyph.width
        height = ascender - descender

        content = dedent(f'''\
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 {-ascender} {width} {height}">
                <g transform="scale(1, -1)">
                    <path d="{svg_path_pen.getCommands()}"/>
                </g>
            </svg>
        ''')
        with open(output_path + str(c) + ".svg", 'w') as f:
            f.write(content) 
```

上記関数はcmapテーブルをループで回して全てのグリフをSVGとして保存する関数です。

```python
save_all_glyph_as_svg(font)
```

実行すると`output_svg`フォルダにSVGが保存されます。
![Screen Shot 2018-12-31 at 20.42.31.png](https://qiita-image-store.s3.amazonaws.com/0/165335/8ea65fa1-f2ad-c375-b504-b1c8a792983b.png)


## 3. cairosvgを使ってSVGをPNGに変換する
全グリフをSVGで保存できたら次はPNGに変換します。cariosvgもpipでインストールできます。

```bash
pip install cairosvg
``` 

さて、cariosvgにはSVGをPNGに変換するsvg2pngという関数が用意されています。

```python
svg2png(url="SVGのパス", write_to="出力先のパス")
```

先ほどの`output_svg`の全SVGをPNGに変換する関数を作成します。

```python
from cairosvg import svg2png
import os


# SVGをPNGに変換する
def convert_all_svg_to_png():
    path = "output_svg"
    files = os.listdir(path)
    files_file = [f for f in files if os.path.isfile(os.path.join(path, f))]
    for fname in files_file:
        bname, ext =  os.path.splitext(fname)
        
        try:
            svg2png(url='output_svg/' + bname + '.svg', write_to='output_png/' + bname + '.png')
        except:
            print(fname)
    return 
```

上記処理ではtry〜 exceptでsvg2pngの例外処理を行っています。これはsvg2pngがゼロ幅スペースのユニコードで異常終了するからです。以下がsvg2pngで落ちると思われるコードになります。
uni200B
uniE9D7
uniE9D6
uniE9D5
uni035F
uni035D
uni035E
uni0009

では、関数を実行します。

```python
convert_all_svg_to_png()
```

見事、SVGがPNGに変換されました。
![Screen Shot 2018-12-31 at 20.42.40.png](https://qiita-image-store.s3.amazonaws.com/0/165335/dcb299b3-53b0-8e20-8a19-d4cc0d6b527e.png)


4. PNGをリサイズして縮小する
上記までの処理でttfをpngに変換する目的は達成していますが、この状態だと非常にサイズがでかいです。

状況に応じて画像をリサイズする必要があるかもしれませんので以下処理を書き残しておきます。以下の関数は`output_png`フォルダの全画像ファイルのサイズを20/1のサイズに変更します。

```python
import os
import glob
from PIL import Image


# PNGをリサイズする
path = "output_png"
files = os.listdir(path)
files_file = [f for f in files if os.path.isfile(os.path.join(path, f))]
for fname in files_file:
    bname, ext =  os.path.splitext(fname)
    if ext != ".png":
        continue
    img = Image.open("./output_png/" + bname + ext)
    img_resize = img.resize((int(img.width/20), int(img.height/20)))
    img_resize.save("./output_resize/" + bname + "_herf" + ext)
```

これで、20分の1のサイズで文字ファイルを全部画像にすることができました。このあと、HOGを抽出してクラスタリングしたり、AAからうまいことHOGで表せるようにしたいと思います。
それでは。