Pythonでフォントを作る


ワープロ専用機風フォントを作ったときに色々調べたのでまとめてみます。

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

基礎知識

OpenType

OpenTypeは現在最も広く使われているフォントの規格で、フォントを作る場合は通常これがターゲット形式になります。

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

Unified Font Object (UFO)

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

ライブラリ

defconufo2ftというライブラリを使います。pipでインストールできます。

$ pip install defcon ufo2ft

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

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

フォントを作る

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

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オブジェクトの作成

import defcon
import ufo2ft

font = defcon.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

font.infodefcon.Info オブジェクトで、フォントの基本情報や各種メトリック値を保持します。ここでは最低限の情報として、以下を設定しています。

  • familyName(フォントファミリー名)
  • unitsPerEmemあたりの単位数、1000や1024が一般的)
  • descenderディセンダー、負の値になる)
  • ascenderアセンダー
  • xHeight(エックスハイト、小文字の高さ)
  • capHeight(キャップハイト、大文字の高さ)

設定できる値の一覧や意味は defcon.InfoのドキュメントUFOのfontinfo.plistの仕様 を参照してください。これらの値がOpenTypeのテーブルにどう反映されるかは、ufo2ftのこのあたりを読むとわかるかもしれません。

文字 'a' 用のグリフを追加

glyph = font.newGlyph('a')
glyph.unicode = ord('a')
glyph.width = 1000

次にfont.newGlyph()を呼び出して、フォントに新しいグリフ(字形)を追加します。引数'a'はグリフの名前です。

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

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

グリフにアウトラインを追加

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 はひと繋がりの輪郭線を表し、複数のdefcon.Point で構成されます。ここでは、4つの頂点 (0, -120), (1000, -120), (1000, 880), (0, 880) を直線で結んだ正方形の輪郭線を定義しています。ちなみにSVG等とは異なり、y座標の大きいほうが上になります。

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

OTFファイルを書き出す

otf = ufo2ft.compileOTF(font)
otf.save('sample.otf')

最後に、ufo2ft.compileOTF()メソッドでOpenType形式にコンパイルし、save()でファイルに書き出します。

作成したフォントの確認

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

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

test.html
<!DOCTYPE html>
<style>
  @font-face {
      font-family: 'sample';
      src: url('sample.otf');
  }
  p {
      font-family: 'sample';
  }
</style>
<p>
  abcabc
</p>

以下が表示結果です。bとcはグリフがないので、フォールバックフォントで表示されています。