font の「ファイル名」を要求されちゃうのってやぁねぇ

前々からこれやろうやろうと思ってて先延ばしにしてたヤツ。

一応 Windows の話だけれど、Unix variant でも多分通用する話。


フォント指定が「ファミリ」と「スタイル」のペアで渡せるものは楽だ。「色んなとこで同じ知識が通用するから」ね。css だってそうであろ。matplotlib もこのタイプ

けど「フォントごときについてはあんまし頑張ってないインフラ」には、ファイル名そのものを要求するものも多い。PIL (or Pillow) もそうだし、graphviz もそうだし、遠い記憶では svg も確かそうだったような。

ファイル名で「自明」な場合は別に困らんのよ。「DejaVuSansMono-BoldOblique.ttf」なんて名前が付いてりゃぁ、およそ見当が付くであろ。けど「cour.ttf」が「Courier New の Regular」であると確信を持てるか? そうなのだ、Windows は 21 世紀になってもいまだに「8+3」の呪縛から逃れられない。歴史あるフォントほど名前から中身を想像出来ないよ、っと。

今になってこんなことをやろうと思ったのはまさしくその「cour.ttf」が張本人。つまり mono space のフォントを Pillow から使いたかったんだけれど、「Microsoft Windows での mono space は Courier New」だとわかったとしても、それが「cour.ttf」である、という情報は一発では見つからない。そりゃそーだ、Windows の普通のアプリケーションで「フォントファイル名」を要求するユーザインターフェイスなんか皆無だから。

コントロールパネルでもこうな:

パスは頑なに見せないようにしてるし、無論それが正解。ファイル名を要求しちゃってることの方が本来は「愚の骨頂」なわけで。

まぁ「ファイル名を要求しちゃうヤツ」のためだけに、ほんの少し情報が欲しいだけだからな、すぐに一覧出来るスクリプトさえありゃぁいいか、てことで:

enum_windows_fonts.py
 1 import os
 2 from PIL.ImageFont import FreeTypeFont
 3 import matplotlib.font_manager as FM
 4 
 5 if __name__ == '__main__':
 6 
 7     _all_fonts = set()
 8     _dejav = set()
 9     
10     ttf_files = FM.list_fonts(FM.win32FontDirectory(), ["ttf", "ttc"])
11     
12     for fn in ttf_files:
13         ttf = FreeTypeFont(font=fn)
14         dn, _, bn = fn.rpartition(os.sep)
15         
16         if bn.upper() in _dejav:
17             # the same but having different case should be the same...
18             # really? i don't know...
19             continue
20         
21         _dejav.add(bn.upper())
22         family, style = ttf.getname()
23         _all_fonts.add((family, style, bn))
24     
25     for family, style, bn in sorted(_all_fonts):
26         print("{:36s}|{:24s}|{}".format(family, style, bn))
 1 me@host: ~$ python enum_windows_fonts.py
 2 (...snip...)
 3 Courier New                         |Bold                    |courbd.ttf
 4 Courier New                         |Bold Italic             |courbi.ttf
 5 Courier New                         |Italic                  |couri.ttf
 6 Courier New                         |Regular                 |cour.ttf
 7 (...snip...)
 8 MS Gothic                           |Regular                 |msgothic.ttc
 9 MS Mincho                           |Regular                 |msmincho.ttc
10 (...snip...)
11 Meiryo                              |Bold                    |meiryob.ttc
12 Meiryo                              |Regular                 |meiryo.ttc
13 (...snip...)
14 Times New Roman                     |Bold                    |timesbd.ttf
15 Times New Roman                     |Bold Italic             |timesbi.ttf
16 Times New Roman                     |Italic                  |timesi.ttf
17 Times New Roman                     |Regular                 |times.ttf
18 (...snip...)

名前とスタイル、なのが今ひとつというか「惜しい」が、まぁ別にいいか、てことで。(脳内でわかってりゃよかろうと。)

ワタシにしては珍しく、全然出力の再利用性は考えてなくて、コンソールに出力して読みやすい形に整形してる。そういう使い方しかしないだろうなぁ、と思ったんで。だって何十種類も使いこなすわけじゃないじゃん、たまーに「いつものとは違う一つ」を使いたいだけよね、今回のワタシの「cour.ttf」みたいにさ。

やろうと思えばまさに「ファミリとスタイルから候補一覧」みたいなスクリプトは書けるけれど、別に全部出てる中から探り当てるの、大変ではないもん。アタシの場合は less にパイプして使うんで、less の検索で済ませちゃう。

ちなみにスクリプトが matplotlib 使ってみたり Pillow 使ってみたりとしてること自体が「各 OSS の得手不得手」を表していたりする。matplotlib はファイルそのものの列挙と検索が得意だが、「情報一覧」は(隠蔽されてるために)取れず、Pillow はファイル名を与えれば情報をくれるが検索のサポートはないし、(場所は知っているくせに)一覧はさせてくれない。


2017-07-27追記:
ここに少し詳しく書いておいたが、face 指定出来るのね。のでこの方がいい:

enum_windows_fonts.py
 1 #! /bin/env python
 2 import os
 3 from PIL.ImageFont import FreeTypeFont
 4 import matplotlib.font_manager as FM
 5 
 6 if __name__ == '__main__':
 7 
 8     _all_fonts = set()
 9     _dejav = set()
10     
11     ttf_files = FM.list_fonts(FM.win32FontDirectory(), ["ttf", "ttc"])
12     
13     for fn in ttf_files:
14         for i in range(10):
15             try:
16                 ttf = FreeTypeFont(font=fn, index=i)
17             except IOError as e:
18                 break
19             dn, _, bn = fn.rpartition(os.sep)
20             
21             if (bn.upper(), i) in _dejav:
22                 # the same but having different case should be the same...
23                 # really? i don't know...
24                 continue
25             
26             _dejav.add((bn.upper(), i))
27             family, style = ttf.getname()
28             _all_fonts.add((bn, i, family, style))
29     
30     for bn, i, family, style in sorted(_all_fonts):
31         print("{:36s}|{:24s}|{}, face={}".format(family, style, bn, i))
 1 me@host: ~$ python enum_windows_fonts.py
 2 (...snip...)
 3 Batang                              |Regular                 |batang.ttc, face=0
 4 BatangChe                           |Regular                 |batang.ttc, face=1
 5 Gungsuh                             |Regular                 |batang.ttc, face=2
 6 GungsuhChe                          |Regular                 |batang.ttc, face=3
 7 (...snip...)
 8 Courier New                         |Regular                 |cour.ttf, face=0
 9 Courier New                         |Bold                    |courbd.ttf, face=0
10 Courier New                         |Bold Italic             |courbi.ttf, face=0
11 Courier New                         |Italic                  |couri.ttf, face=0
12 (...snip...)
13 Meiryo                              |Regular                 |meiryo.ttc, face=0
14 Meiryo                              |Italic                  |meiryo.ttc, face=1
15 Meiryo UI                           |Regular                 |meiryo.ttc, face=2
16 Meiryo UI                           |Italic                  |meiryo.ttc, face=3
17 Meiryo                              |Bold                    |meiryob.ttc, face=0
18 Meiryo                              |Bold Italic             |meiryob.ttc, face=1
19 Meiryo UI                           |Bold                    |meiryob.ttc, face=2
20 Meiryo UI                           |Bold Italic             |meiryob.ttc, face=3
21 (...snip...)
22 MS Mincho                           |Regular                 |msmincho.ttc, face=0
23 MS PMincho                          |Regular                 |msmincho.ttc, face=1
24 (...snip...)