AndroidJava/フォントを変える

アプリのフォントを自前で用意(2019-04-07)




はじめに

そもそも
フォントとは何かのイメージが無いときついかもしれません。
ふわっとした説明をすると

65="A", 66="B", 67="C", 68="D"...

という風に全ての文字に通番が振ってあって、*1
フォントを作成するデザイナーが 65番 に対応した
画像を用意している
それがフォントになります。

  • フォントを変えると、文字表示が変わる
    おなじ 'A' (=65番) を表示するにしても、
    MS明朝だったり、IPAゴシックだったり、色々
  • そのフォントセットも、誰かが作ったものだから、各々にライセンスがある
  • フォントセットが対応していない文字は表示できない
    (だからフォントの種類によっては、日本語が表示できない)

以上、ふわっとした説明です。
文字コードとか、ライセンス(著作権)とか、
フォントをこね回す過程で、色々覚えることになるかもしれません。

画像や音楽と同じように、フォントも変えて
アプリの雰囲気を変えてみましょう!
...と締めくくる予定だったんですが、先に結論を書いておくと、
(ちゃんと、検討した内容やフォント変える方法は後述しますが)
OSにデフォルトで入っているフォントが、普通は一番読みやすいんじゃないかなぁ〜、なんて思っています...
すこし試してみた感想ですが

変えるメリット?

  • アプリの雰囲気がマッチする
  • デザインがしやすい。こちらが意図したとおりに表示される。
    (利用者がAndroid端末でどんなフォント使っているのかは分からないので、
     フォントもこちらで用意すれば、余計な心配が減る)

変えるデメリット?

  • フォントがメモリを食う(アプリのインストールサイズが増える)
  • フォントを探す手間 / 調整する手間 がかかる

アプリの目的によるんでしょうね。
デザイン上は、フォントのインストール必須って言っているようなものですが、
画像のみでテキストを使わないミニゲームアプリとかなら、フォント要らないでしょうし。
(文字も画像(BitmapかPath)で用意する)


それに、フリーにこだわらずに商用のかっこいいフォントセットを使うなら、むしろフォントが主役かもしれないですし。

ライセンスについて

フリーのフォントと言っても、条件はあるので
(例えば、内容を変えてもいいけれど、
 変えたものも必ずフリーライセンスとして公開しなければいけない、等)
いずれにせよ使う前に各々の規約を読む必要が有ります。

そして、フォントの作成に使うソフトにもライセンスが有って、
後述する birdfont は有料と無料でライセンスが違うので注意が必要です。(詳細は birdfont.orgで確認できます)

いずれにせよ、頑張って規約を... [worried]

ひとまず、IPAフォントあたりを試しに使ってみるのはいかがでしょうか。
他には、はんなり明朝とかもステキですよね。
IPAフォントライセンスは日本語もあったので、頑張れば読めないこともないです。

前置きが長くなりましたが
次から実装例です。

用意したフォントに差し替える例@Java

まずは、持ってきたフォントをそのまま読み込みます。
例は、IPAフォントを読み込んでいます。

// typefaceを指定する方法
Typeface typeface;
 
// APIが許せば、resourceから取得可能?
//if(Build.VERSION.SDK_INT >= 26)
// typeface = context.getResources().getFont(R.font.ipam);
 
// わたしの環境はAPIバージョンが足りないので、asset から取得
// 事前に assets フォルダにフォントを置いてから、以下
final Typeface typeface = Typeface.createFromAsset(getAssets(), "font/ipam.ttf");
   
// これを、drawText() に渡す Paint に設定するイメージで //Paint paint = new Paint();
//paint.setTypeface(typeface);

ここでフォントセットを置く場所の候補として、
resource と asset の二種類の方法が出ます(上記ソースの前半、後半がそれ)
違いについては「android assets resource difference」あたりでWeb検索できます。

  • 日本語 と それ以外(英語) で分けるのが res の方が楽そう
  • res だと リソースID指定ができて、何かと便利?
  • ただし API 26 以降でしか res フォントはできなそう

今回 res ではなく assets で試してます。
assets を使う場合は、 おそらくAndroid Studio のデフォルトでは作成されていないので、
[File] - [New] - [Folder] - [Assets Folder] で追加しましょう。
上記の例だと、その下に font ディレクトリをてきとうに作っています。

このようにresを使わない方法だと、
何かやるたびに getAssets() しないといけないので、
Typeface インスタンスを使い回せるように保持する必要があるのですが、それすら面倒な場合は、
一回で済むように、特定のリソースを上書きしてしまう方法もあります。

以下、アプリ起動時のどこかで(メイン Activity の onCreate 等の中で)

   // フォントの変更
   try {
       final Typeface typeface = Typeface.createFromAsset(getAssets(), "font/ipam.ttf");
       // 既存の SERIF へ上書きしてしまう
       final Field declaredField = Typeface.class.getDeclaredField("SERIF");
       declaredField.setAccessible(true);
       declaredField.set(null, typeface);
   } catch (Exception e){
       Log.e("can not change font:" + e);
       // 例外処理を入れます
   }
   // 無事読めたら、以降は Typeface.SERIF; を読み込む

これで仕込んだ後に、
drawText() 等に使う Paint 変数には、上記を渡します。

Paint paint = new Paint();
paint.setTypeface(Typeface.SERIF);

(・・・結局、setTypefaceするなら、
 おとなしく保存した typefaceインスタンスを使い回せば良いですね)

フォントの削除@Linux

前述のIPAフォントをはじめとして、ものによっては、そのまま使うとサイズが大きくなってしまいます。
(この、サイズっていうのは文字の大きさではなく、ipam.ttf ファイル自体のサイズのことです)

初代ドラクエは容量削減のために使える文字を絞り込んでいたという話を思い出しました。

ワンタッチでいい感じに容量を削減してくれるフリーウェアもありましたが、
そもそも何がある&削るか確認したい意図もあり、今回は自力で削ってみる方針です。
実際見てみると分かるのですが、日本語はとにかく漢字があるので、文字数が多いようですね。

これは仕様ではなく、実動作からの経験ですが
(なので、保証はないのですが、)
前述のjavaでの例、 assets で自分で追加したフォントには「存在しない」文字を表示する場合は
(例えばカナ以外全削除したフォントセットで、漢字を表示したとき)
デフォルトのフォントセットで表示されるようです。
何も出ないとかExceptionで落ちるとかでは無く。
だから、使わなさそうなフォントはどんどん削っても良さそう?

というわけで、削除です。

うちのLubuntu 18.04 環境で、FontForge を
パッケージマネージャからインストールしました。

FontForge(英語)を起動してフォントを開いてみると(ttfファイル等を開いてみると)
初見ではその数に愕然(がくぜん)とするかもしれませんが*2
キーボードでPageDown キーを連打していれば、正直、それほどでもないです。
空欄は Ctrl + Alt + ] キーでスキップできます。

シフトキー押しながら画面移動して、
要らないフォントを選んだ状態で、右クリック、Clear で一気に消せます。
ひと通り終わったら [File] - [Generate Fonts] で保存する、という流れです。

これも私見、というか自分向けにメモですが、
漢字だと 4e00 から 909f 前後まで
f900-fffd は 互換特殊文字で、ff01-ff9f は保留。ほかは全消し。
20000-2ffff は追加漢字面らしいですが、削除で。
10ffff 以降は無視(削除)という軽いノリで突き進んでみました。

こんな削除作業を、前述のIPA明朝フォントでやってみたんですが、
それでもサイズにして 6MB近く残っています。
日本語って文字が多くて大変ですね。

フォントの作成@Linux

FontForgeを使えば、自分独自のフォントを作成することができます
が、
うちの環境は、マシンパワーが弱いせいか?ガンガン落ちます。
あと、少し慣れが必要そうです。

そこで次は birdfont をインストールして試しました。
が、起動できません。

$ birdfont
birdfont: error while loading shared libraries: libxmlbird.so.1.0: cannot open shared object file: No such file or directory

GUIではなく、コマンドラインから実行して起動できなかった理由がわかりました。
libが無いとのことです...が、find すると、
それらしいlib は他にあるので ln でリンクを作って(強引に)起動できるようになりました。

$ find /usr -type f -name "libxmlbird*"  
/usr/lib/x86_64-linux-gnu/libxmlbird.so.1.3
$ sudo ln -s /usr/lib/x86_64-linux-gnu/libxmlbird.so.1.3 /usr/lib/x86_64-linux-gnu/libxmlbird.so.1.0 

編集作業に関しては私は birdfont の方がやりやすかったです。
削除作業(要らないフォントを消す作業)だけなら
FontForgeの方が動作が軽くてやりやすく、用途にあわせて使い分ける印象でした。

これらの方法で、自前のフォントセットを用意して、
前述の java の例のようにアプリに読み込んで、
アプリの雰囲気を変えてみましょう。

余談

ここまでやれたなら、色々応用できそうな感じがします。
時間経過でsetTypefaceしつつフォントを差し替えていく、なんてこともできますし。
例えば Helpの4文字だけ、頑張ってフォントを3種類作って、
徐々に文字が、三段階に溶けていく演出、とか。

また、部分的に変えるだけなら、
フォントで作らなくてもBitmapやらPathやらを用意してもいいです。
文字の大きさの絵を、文章内に貼り付ければいいので。
具体的には、
"ABC" という文字列をCanvas.drawText() するとき、Bだけオリジナルのフォントにするなら

  • まず"A"だけ drawTextする
    canvas.drawText("A", x, y, paint); で書いたとします。
  • "B"を、あらかじめ用意したpng画像なりPathなりで書く。
    • Bの始点X座標は、x + paint.measureText("A")
    • Bの始点Y座標は、y + paint.getFontMetrics().top
    • Bの縦の高さは paint.getFontMetrics.bottom - paint.getFontMetrics.top
    • 幅はよしなに。縦の高さと同じにすればよいかと。
  • "C"を書く
    • CのX座標は、Bのやつ+Bの横幅

文字をdrawTextする際の座標が、
上記のように getFontMetrics.top がマイナスの値で、
文字の高さ全体は bottom - top で取得できる、のがポイントかと。
(または paint.getTextSize() で取得しても)


やってみた感想としては、
商用目的ならば、時間も含めたコストとの戦いになるので、
フォントを(フォントに限らず、素材全般?)購入したほうが結果安くつくような気がしました。
趣味でやるぶんには、フォントを探す楽しみも含めて、
色々試してみたいところですね。


*1 具体的な番号はLinuxの場合は man ascii とかで見れます。後述のFontForgeはその全てのフォントを確認したり編集したりするやつです。
*2 要らない文字を削るだけなら、それほどでもないですが、オリジナルのフォントを作るのは...作っている人はすごいと思います。