46
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

『BIZ UDPゴシック + Inter = 最強』って話。

Last updated at Posted at 2022-12-18

こんにちは。以前に HackGen を公開した人です。

ふと思い立って合成してみたフォントが、美しすぎました。
モリサワのユニバーサルデザインなフォント BIZ UDPゴシック と、美しいUIフォントとしてお馴染みの Inter をくっつけてみました。

今回は、そのフォントのご紹介がてら、Python を用いてオレオレフォントを合成してつくる流れを一挙解説していきたいと思います。

BIZTERの特徴

今回は、 BIZ UDPゴシック + Inter で、それぞれの名前から一部を抜いて BIZTER と名付けました。

大好きな Inter と大好きな BIZ UDPゴシック の組み合わせ。やはり、とても読みやすいフォントになりました。ご興味があれば、ぜひお試しください。
ダウンロードはこちら

image.png

以下のような特徴があります。

  • コンピュータ画面上での読みやすさにこだわり抜かれた英語フォント Inter を英数字部分に使用
  • 日本語文字など Inter に存在しない文字を、モリサワ社がユニバーサルデザインを標榜して制作された日本語フォント BIZ UDPゴシック で補完

上記それぞれがプロポーショナルフォントなので、合成した BIZTER もプロポーショナルフォントになっています。

BIZ UDゴシックが SIL Open Font License 1.1 で登場した のが話題になったのを覚えていらっしゃる方も多いと思いますが、 UDEV Gothic に続き今一度、BIZ UDゴシックを活用して何かリリースしたかった、という思いがありました。

折角なら、システマチックに合成フォントを作るノウハウをここらでまとめてみようかとも考えました。ついでになかなか出来ていなかった Python を使っての合成もやってみたかった。

「記事のわかり易さも意識し、特に工夫や苦労が無くシンプルに合成して完成度が高まるものってなんだろうか」と考え、BIZ UDPゴシック + Inter の着想に至りました。

Pythonを使った合成用プログラム

さて、以降で今回合成に用いたPythonプログラムについて解説していきます。

前提

以降の解説では、以下について触れていきます。

  • どんなパッケージやライブラリを使うのか
  • どんな設定値を意識して合成すれば、フォントとして成り立つのか
  • 省力的に合成プロポーショナルフォントをつくる方法

反対に、以下については触れません。

  • 読者が Python の基礎文法が知っている/分かることを想定し、Python の書き方は説明しません
  • 固定幅フォントをつくる方法
  • フォントをゼロから作れるような広い知識

大まかな流れ

以下のような流れで作っていきます。

  • フォントの選定
    • 公開しようとしている場合は、そのフォントのライセンスもしっかり確認すること
  • フォント関連ツールをインストールするなどの環境準備
  • 合成用プログラムのコーディング

次項から詳しく見ていきましょう。

フォントの選定とライセンスの確認

まずはフォント選びです。

簡単に作るためには、プロポーショナルフォント + プロポーショナルフォント で選ぶのがよいでしょう。今回は前述の通り、 BIZ UDPゴシック と Inter を使います。

また、この選定のタイミングで「自分はこのフォントを公開するつもりなのか?」を考えておきましょう。場合によってはライセンス違反により公開中止を余儀なくされる可能性があるからです。

ライセンスを意識するときにオススメなのは、 SIL Open Font Licenseで公開されているフォントだけを合成元として使うこと です。
このライセンスは Free Software Foundation によってフリーソフトウェアライセンスと認められており、公開物 (今回でいえば合成後のフォント) でこのライセンスを保持する場合に限り、フォントの使用、改変、再配布、ドキュメントへの埋め込みなどを自由に行うことができます。

もちろん、BIZ UDPゴシック と Inter は SIL Open Font License によって公開されています。

環境準備

今回は、最低限の準備で作り始められることを意識しました。

Ubuntu 22.04.1 LTS のデスクトップ環境を VirtualBox や Hyper-V で起動し、以下を実行して必要なツールをインストールします。(試していませんが、おそらく Ubuntu 20 でもいけると思います)

sudo apt install python3 python3-pip fontforge python3-fontforge ttfautohint

次いで、以下のコマンドでビルドできるはずです。 (v0.0.1 時点では、 build_tmp という雑なディレクトリに生成されるようになっています)

git clone https://github.com/yuru7/BIZTER.git
cd BIZTER && python3 build.py

試しに BIZTER/source_fonts ディレクトリにあるフォントや、ビルドされた BIZTER なんかを前段でインストールした FontForge というフォント制作ツールで開いてみましょう。

image.png

FontForgeでは、以降で解説しているメタデータなどを一通り GUI で設定することができます。というより実際は、FontForge の GUI 処理でできることを Python のコードに置き換えているのです。

あとは好きなテキストエディタをインストールしておきましょう。おすすめは Ubuntu でも手軽にインストールできる VSCode です。

コーディング

この項では、 v0.0.1 のときの BIZTER ビルドスクリプト の内容に沿って、解説してきます。
コードの全体像は v0.0.1/build.py をご覧ください。


3-6行目
import subprocess
from typing import Any, Tuple

import fontforge

3-6行目、import文。
環境準備の項で python3-fontforge をインストールしたことで、 import fontforge が使えるようになっています。

8-31行目
VERSION = "v0.0.1"
FONT_NAME = "BIZTER"

BUILD_TMP = "build_tmp"
SOURCE_DIR = "source_fonts"
SOURCE_FONT_JP = "BIZUDPGothic-{}.ttf"
SOURCE_FONT_EN = "Inter-{}.ttf"

EM_ASCENT = 1782
EM_DESCENT = 266

FONT_ASCENT = EM_ASCENT + 60
FONT_DESCENT = EM_DESCENT + 170

COPYRIGHT = """[Inter]
Copyright (c) 2020 The Inter Project Authors (https://github.com/rsms/inter)
[BIZ UDPGothic]
Copyright 2022 The BIZ UDGothic Project Authors (https://github.com/googlefonts/morisawa-biz-ud-gothic)
[BIZTER]
Copyright 2022 Yuko Otawara
"""

8-31行目、固定値となる文字列などの定義。
それぞれの設定値については、これらの定数を使う場面で説明します。

なお COPYRIGHT 定数については、フォントに埋め込んでいる著作権者情報になるので、合成元のフォントと自身の情報を合わせて載せておきましょう。

97-122行目
def main():
    # TODO: tmpフォルダを作って final で削除する

    for weight in ("Regular", "Bold"):
        jp_font, en_font = open_font(weight)

        remove_duplicate_glyphs(jp_font, en_font)

        font = merge_fonts(jp_font, en_font, weight)

        edit_meta_data(font, weight)

        font.generate(f"{BUILD_TMP}/gen_{FONT_NAME}-{weight}.ttf")

        subprocess.run(
            (
                "ttfautohint",
                "--dehint",
                f"{BUILD_TMP}/gen_{FONT_NAME}-{weight}.ttf",
                f"{BUILD_TMP}/{FONT_NAME}-{weight}.ttf",
            )
        )


if __name__ == "__main__":
    main()

97-122行目、main メソッド。
これが全体の処理の流れになります。(TODO が残っているのはご愛嬌…)

以下のような流れになっています。

  1. jp_font (BIZ UDPゴシック) と en_font (Inter) のフォントファイルを開く
  2. en_font を jp_font の中にすべて合成するため、jp_font から en_font と重複する分のグリフ (ASCII文字とか) を削除する
  3. jp_font と en_font を合成する
  4. 著作権表示や行の高さを示すメタデータを編集する
  5. 合成したフォントを ttf ファイルとして出力する
  6. 出力した ttf ファイルからヒンティング情報を削除する

上記までの流れを、 "Regular" と "Bold" の2種類のウェイト分をループで処理します。

33-41行目
def open_font(weight) -> Tuple[Any, Any]:
    """フォントファイルを開く"""
    jp_font = fontforge.open(f"{SOURCE_DIR}/{SOURCE_FONT_JP.format(weight)}")
    if weight == "Bold":
        # 太さを合わせるため、InterはSemiBoldを使う
        en_font = fontforge.open(f"{SOURCE_DIR}/{SOURCE_FONT_EN.format('Semi'+weight)}")
    else:
        en_font = fontforge.open(f"{SOURCE_DIR}/{SOURCE_FONT_EN.format(weight)}")
    return jp_font, en_font

33-41行目では、BIZ UDPゴシック と Inter を開き、それぞれを変数に格納しています。Inter の Bold では BIZ UDPゴシックの Bold に対して太すぎるため、SemiBold を使うようにしています。

取得した jp_font, en_font は同時に返却します。プログラミングは Java から入った自分にとっては見慣れなかった書き方ですが、何気に便利ですよね。

44-52行目
def remove_duplicate_glyphs(jp_font, en_font):
    """Interと重複しているグリフを削除する"""
    for g in en_font.glyphs():
        if not g.isWorthOutputting():
            continue
        unicode = int(g.unicode)
        if unicode >= 0:
            for g_jp in jp_font.selection.select(unicode).byGlyphs:
                g_jp.clear()

44-52行目では、jp_font (BIZ UDPゴシック) から不要グリフを削除しています。

今回は、jp_font (BIZ UDPゴシック) に対して en_font (Inter) を合成する手法を取ります。 そうすることで、 BIZ UDPゴシックに含まれる 異体字シーケンス などのメタデータを簡単に流用できる からです。

そのためにまず、半角英数字などのような en_font から取り込みたいグリフの位置にある jp_font 上のグリフを削除します。今回は en_font をすべて取り込みたいので、「en_font 側に存在し、かつ jp_font 側にも存在するグリフを jp_font から削除する」ようにします。

55-64行目
def merge_fonts(jp_font, en_font, weight) -> Any:
    """英語フォントと日本語フォントをマージする"""
    # マージするためにemを揃える
    em_size = EM_ASCENT + EM_DESCENT
    jp_font.em = em_size
    en_font.em = em_size

    en_font.generate(f"{BUILD_TMP}/modified_{SOURCE_FONT_EN.format(weight)}")
    jp_font.mergeFonts(f"{BUILD_TMP}/modified_{SOURCE_FONT_EN.format(weight)}")
    return jp_font

55-64行目、ここまで 下ごしらえをしてきた jp_font と en_font をいよいよマージします。

マージする際に em という値を両フォントで揃えます。em 値は、1つ1つのグリフの縦方向の高さ・深さを足した情報です。高さのことを Ascent, 深さのことを Descent といいます。

image.png

em 値を揃えたら、一度 en_font を ttf ファイルとして生成し、それを jp_font に mergeFonts メソッドでマージするようにします。

67-94行目
def edit_meta_data(font, weight: str):
    """フォント内のメタデータを編集する"""
    font.ascent = EM_ASCENT
    font.descent = EM_DESCENT
    font.os2_typoascent = EM_ASCENT
    font.os2_typodescent = -EM_DESCENT

    font.hhea_ascent = FONT_ASCENT
    font.hhea_descent = -FONT_DESCENT
    font.os2_winascent = FONT_ASCENT
    font.os2_windescent = FONT_DESCENT
    font.hhea_linegap = 0
    font.os2_typolinegap = 0

    font.sfnt_names = (
        (
            "English (US)",
            "License",
            "This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFL",
        ),
        ("English (US)", "License URL", "http://scripts.sil.org/OFL"),
        ("English (US)", "Version", f"{FONT_NAME} {VERSION}"),
    )
    font.familyname = FONT_NAME
    font.fontname = f"{FONT_NAME}-{weight}"
    font.fullname = f"{FONT_NAME} {weight}"
    font.os2_vendor = "TWR"
    font.copyright = COPYRIGHT

67-94行目、メタデータを修正します。
ここで触れるメタデータとは、グリフ以外の行の高さを示す情報、著作権情報、フォントファミリー名などの情報です。

前述したとおり、今回は BIZ UDPゴシック のメタデータを流用することで省力的に作ろうとしているので、その前提で最低限必要な修正のみを加えています。

フォントファイルには色々な設定値テーブルが含まれています。OS/2 テーブルや hhea テーブルなど、複数のテーブルに高さ・深さを示す設定値があるため、上記コードにおいてもそれぞれに設定しています。 (参考: Microsoft ドキュメントの OS/2 テーブルhhea テーブル)

ここではひとまず、

  • ascent, descentos2_typoascent, os2_typodescent には同じ値 (ただし os2_typodescent は負の値で指定)
  • hhea_ascent, hhea_descentos2_winascent, os2_windescent には実際に表示される行の高さを指定する。つまり ascent, descent 以上の値にする。 (ただし hhea_descent は負の値で指定)

という関係を理解しておきましょう。

109-118行目
        font.generate(f"{BUILD_TMP}/gen_{FONT_NAME}-{weight}.ttf")

        subprocess.run(
            (
                "ttfautohint",
                "--dehint",
                f"{BUILD_TMP}/gen_{FONT_NAME}-{weight}.ttf",
                f"{BUILD_TMP}/{FONT_NAME}-{weight}.ttf",
            )
        )

109-118行目。
最後に、合成したフォントを ttf ファイルとして出力します。その後ヒンティング情報を削除します。

ヒンティング情報というのは、ディスプレイ上のピクセル配置に合わせて文字のラインを配置し直すための情報です。
それぞれのグリフは、点と点を結ぶ "アウトライン" という線の情報で文字を形作っているため、線がディスプレイのピクセル間にまたがってしまうことで表示が淡くなってしまうことがあります。したがって、ヒンティング情報は特に小さい文字サイズで表示するときにハッキリクッキリな表示品質を得るために使われます。

ヒンティング情報が適用された場合のピクセル上の表示イメージを掴むため、 ttfautohint 配布元の記事 の掲載画像を引用します。ヒンティングが適用されている真ん中、右側の "a" は、アウトライン上がより黒く表示されるようになっています。

image.png

FontForge での合成の過程で自動でこういったヒンティング情報が付与されるのですが、残念ながら漢字との相性が良くありません。漢字のように縦横に細かに線が伸びているグリフでは、線の位置が上下に1ピクセルずれるだけで印象が大きく変わってしまい、字体のバランスが崩れてしまうからです。

FontForge などのツールを使わずに手動でヒンティング情報を調整できればいいのでしょうが、1字1字で調整を行うとなると、途方もない時間がかかると思われ、個人で行うのはまず無理でしょう…。
ちなみに、このヒンティング情報を独自にチューンアップして Windows 上で明瞭に見えるようにしたのが メイリオ フォントだと言われています。

なお、ここで使っている ttfautohint というツールは本来、ヒンティング情報を自動付与するためのツールですが、オプションを付けて実行することでヒンティング情報を外すことができます。

終わりに

今回は記事化することを念頭に置き、FontForge ✕ Python を使って、平易な流れでコードを用意してみました。

Python で FontForge を操作するのは初めてでしたが、慣れてみれば HackGen などのように ShellScript で作るよりもシンプルにできることが分かりました。固定幅の合成フォントを作るときには FontForge だけでなく、 fontools という Python ライブラリを用いていたのですが、Python プログラムの中で一緒くたにそれらも使えることを考えると、やはり Python で書くのが正解なのでしょうね。

なお前提にも書きましたが、ここでやったことだけでは固定幅フォントは作れません。この記事では詳細は書きませんが、上記のように合成することの他に、

  • OS/2 テーブルの xAvgCharWidth フィールドの値を半角文字の幅にする
  • OS/2 テーブルの PANOSE フィールドの bit 列で、monospace (固定幅) であることを示す

といったメタデータの設定が必要になってきます。
このあたりは拙作 HackGen, PlemolJP, UDEV Gothic の他、miiton 氏の Cica などのプログラミングフォントのリポジトリを覗いてみると参考になるかもしれません。

参考リンク

46
23
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?