Pythonでフォントを作る

[ワープロ専用機風フォント](https://irori.github.io/wapuro-mincho/)を作ったときに色々調べたのでまとめてみます。

自分でフォントをデザインするときはGUIのフォントエディタを使うのだと思いますが、この記事ではアウトラインデータが手元にあって（もしくはプログラムで生成するつもりで）、そこからPythonスクリプトでフォントを作る方法を紹介します。

## 基礎知識

### [OpenType](https://docs.microsoft.com/en-us/typography/opentype/spec/)
OpenTypeは現在最も広く使われているフォントの規格で、フォントを作る場合は通常これがターゲット形式になります。

ちなみにWebフォントの形式であるWOFFやWOFF2は独立したフォント形式というわけではなく、OpenTypeに含まれるデータを再構成して圧縮率を上げたものです。

### [Unified Font Object (UFO)](http://unifiedfontobject.org/)
OpenTypeは完成したフォントを配布するためのフォーマットであり、編集用として必ずしも便利なものではありません。各フォントエディタは編集用にはそれぞれ独自の形式を使っていましたが、こうした用途でのオープンな汎用フォーマットとして考えられたのがUFOです。フォントを扱うPythonライブラリでも目にすることが多い形式です。

## ライブラリ
[`defcon`](https://github.com/typesupply/defcon/)と[`ufo2ft`](https://github.com/googlei18n/ufo2ft)というライブラリを使います。`pip`でインストールできます。

```shell
$ pip install defcon ufo2ft
```

`defcon`はUFOデータをPythonから便利に扱えるライブラリです。フォントエディタなどでオブジェクトモデルとして使われることを想定しているようです。

`ufo2ft`は、`defcon`のフォントオブジェクトからOpenType形式への変換を提供するライブラリです。

## フォントを作る
以下は、`ufo2ft`でフォントを生成する最小限のプログラムです。文字 `a` を塗りつぶされた正方形で表示するだけのフォントを生成します。

```python:fontgen.py
#!/usr/bin/env python
import defcon
import ufo2ft

font = defcon.Font()

# フォント名、基本メトリックの設定
font.info.familyName = 'Sample Font'
font.info.unitsPerEm = 1000
font.info.descender = -120
font.info.ascender = 880
font.info.xHeight = 500
font.info.capHeight = 800

# 文字 'a' 用のグリフを追加
glyph = font.newGlyph('a')
glyph.unicode = ord('a')
glyph.width = 1000

# グリフにアウトラインを追加
contour = defcon.Contour()
contour.appendPoint(defcon.Point((0, -120), 'line'))
contour.appendPoint(defcon.Point((1000, -120), 'line'))
contour.appendPoint(defcon.Point((1000, 880), 'line'))
contour.appendPoint(defcon.Point((0, 880), 'line'))
glyph.appendContour(contour)

# OTFファイルを書き出す
otf = ufo2ft.compileOTF(font)
otf.save('sample.otf')
```

部分ごとに見ていきましょう。

### Fontオブジェクトの作成
```python
import defcon
import ufo2ft

font = defcon.Font()
```
必要なライブラリをインポートして、[`defcon.Font`](http://ts-defcon.readthedocs.io/en/latest/objects/font.html)オブジェクトを作成します。これがフォント全体を表すオブジェクトで、この中にメトリック情報や各文字のデータを設定していきます。

### フォント名、基本メトリックの設定
```python
font.info.familyName = 'Sample Font'
font.info.unitsPerEm = 1000
font.info.descender = -120
font.info.ascender = 880
font.info.xHeight = 500
font.info.capHeight = 800
```
`font.info` は [`defcon.Info`](http://ts-defcon.readthedocs.io/en/latest/objects/info.html) オブジェクトで、フォントの基本情報や各種メトリック値を保持します。ここでは最低限の情報として、以下を設定しています。

- `familyName`（フォントファミリー名）
- `unitsPerEm`（[em](https://ja.wikipedia.org/wiki/Em_(%E5%8D%98%E4%BD%8D))あたりの単位数、1000や1024が一般的）
- `descender`（[ディセンダー](https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%A3%E3%82%BB%E3%83%B3%E3%83%80%E3%83%BC)、負の値になる）
- `ascender`（[アセンダー](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%BB%E3%83%B3%E3%83%80%E3%83%BC)）
- `xHeight`（エックスハイト、小文字の高さ）
- `capHeight`（キャップハイト、大文字の高さ）

設定できる値の一覧や意味は [`defcon.Info`のドキュメント](http://ts-defcon.readthedocs.io/en/latest/objects/info.html) や [UFOの`fontinfo.plist`の仕様](http://unifiedfontobject.org/versions/ufo3/fontinfo.plist/) を参照してください。これらの値がOpenTypeのテーブルにどう反映されるかは、ufo2ftの[このあたり](https://github.com/googlei18n/ufo2ft/blob/master/Lib/ufo2ft/fontInfoData.py)を読むとわかるかもしれません。

### 文字 'a' 用のグリフを追加
```python
glyph = font.newGlyph('a')
glyph.unicode = ord('a')
glyph.width = 1000
```
次に`font.newGlyph()`を呼び出して、フォントに新しいグリフ（字形）を追加します。引数`'a'`はグリフの名前です。

グリフと文字コードとの対応づけは、グリフの`unicode`プロパティにUnicodeコードポイントを設定することで行います。ここでは`a`の文字コード（`ord('a')` = 0x61）を設定しています。

グリフの`width`プロパティには、グリフの幅を設定します。ここでは1em（1 * `unitsPerEm`）を設定しています。

### グリフにアウトラインを追加
```python
contour = defcon.Contour()
contour.appendPoint(defcon.Point((0, -120), 'line'))
contour.appendPoint(defcon.Point((1000, -120), 'line'))
contour.appendPoint(defcon.Point((1000, 880), 'line'))
contour.appendPoint(defcon.Point((0, 880), 'line'))
glyph.appendContour(contour)
```
作成したグリフにアウトライン情報を追加していきます。[`defcon.Contour`](http://ts-defcon.readthedocs.io/en/latest/objects/contour.html) はひと繋がりの輪郭線を表し、複数の[`defcon.Point`](http://ts-defcon.readthedocs.io/en/latest/objects/point.html) で構成されます。ここでは、4つの頂点 (0, -120), (1000, -120), (1000, 880), (0, 880) を直線で結んだ正方形の輪郭線を定義しています。ちなみにSVG等とは異なり、y座標の大きいほうが上になります。

作成した輪郭線は、`appendContour()`でグリフに追加します。

### OTFファイルを書き出す
```python
otf = ufo2ft.compileOTF(font)
otf.save('sample.otf')
```
最後に、`ufo2ft.compileOTF()`メソッドでOpenType形式にコンパイルし、`save()`でファイルに書き出します。

## 作成したフォントの確認
前節のPythonプログラムを実行すると`sample.otf`という名前でフォントファイルが生成されます。

フォントの確認は、ブラウザでWebフォントとして読み込ませてみるのが簡単です。以下のHTMLファイルを`sample.otf`と同じフォルダに置いて、ブラウザで開いてみてください。

```html:test.html
<!DOCTYPE html>
<style>
  @font-face {
      font-family: 'sample';
      src: url('sample.otf');
  }
  p {
      font-family: 'sample';
  }
</style>
<p>
  abcabc
</p>
```
以下が表示結果です。bとcはグリフがないので、フォールバックフォントで表示されています。

<img width="100" alt="表示結果" src="https://qiita-image-store.s3.amazonaws.com/0/65923/1e8200cd-6273-f855-6797-b73de829155e.png">