2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pygameで使える日本語フォントを調べてみた

Posted at

Pygameのデフォルト フォントでは、日本語は表示できない

これは、有名な事実の様です。

a.py.png

次のコードで確認しました。

a.py
import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((320, 180))

font = pygame.font.SysFont(None, 80) #⭐️
surface = font.render("文字化け必至", True, 'white')

done = False
while not done:
    screen.fill('black')
    screen.blit(surface, (10,10))
    pygame.display.update()

    for event in pygame.event.get():
        if event.type == QUIT:
            done = True
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE or event.key == K_q:
                done = True

pygame.quit()

ウインドウバーの『閉じる』、ESCキー か Qキーで終了。

日本語が表示できるフォント名を調べる


前段として、

フォント名の一覧を得る

次の関数で、Pygameで扱えるフォント名のリストが取得できます。

pygame.font.get_fonts()

ただし、順不同のため、以降はフォント名をソートしています。

b.py(使用例)
import pygame
from pygame.locals import *

pygame.init()

for index, fontname in enumerate(sorted(pygame.font.get_fonts())):
    try:
        font = pygame.font.SysFont(fontname, 30)
        print(f"{index+1}\t{fontname}")
    except:
        print(f"{index+1}\t{fontname} is not available!!")

pygame.quit()

pygame.font.SysFontできないフォントがあったため、try/exceptした。
exceptionの内容から日本語を含むパス名のフォントが扱えない可能性があると想定される。しかし、pygame.font.Fontでそのフルパスを指定したが例外は発生しなかった。ナゾだ。

実行例
$ python b.py
pygame 2.5.2 (SDL 2.28.3, Python 3.11.9)
Hello from the pygame community. https://www.pygame.org/contribute.html
1       
2       academyengravedlet
3       albayan
4       albayanpua
5       alnile
6       alnilepua
7       altarikh
8       altarikhpua
9       americantypewriter
10      andalemono
11      applebraille
12      applechancery
13      applecoloremoji
14      applecoloremojiui
15      applegothic
16      applemyungjo
17      applesdgothicneo
18      applesdgothicneoi
19      applesymbols
20      aqua

(略)

360     zapfdingbats
361     zapfino
$ 

先頭にリストアップされた”フォント名が空白”フォントは不明。Windows や Ubtntu では存在しない。

自分のMacだと361種類もあった。

使用するOSやインストールされているフォントの種類によって、Pygameにて日本語が表示できるフォントの数や種類は異なります。

日本語が表示できるフォントは、フォント名から判断できない

一部はフォント名から想定できるとしても、実際に表示させてみないと、本当に日本語が表示できるフォントか否かは判断できません。

次のコードでざっと見てみます。
取得したフォントリストを使って、実際に次々に表示させます。

c.py.gif

c.py
import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((900, 400))

fontList = []
for fontname in sorted(pygame.font.get_fonts()):
    try:
        font = pygame.font.SysFont(fontname, 30)
        fontList.append(fontname)
    except:
        pass

clock = pygame.time.Clock()

fontIndex = 0
done = False
while not done:
    clock.tick(10)
    pygame.display.set_caption(f'[{fontIndex + 1}/{len(fontList)}] {fontList[fontIndex]}')

    screen.fill('black')

    font = pygame.font.SysFont(fontList[fontIndex], 30)
    surface1 = font.render(fontList[fontIndex], True, 'white')
    surface2 = font.render("1234567890", True, 'white')
    surface3 = font.render("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", True, 'white')
    surface4 = font.render("ABCDEFGHIJKLMNOPQRSTUVWXYZ", True, 'white')
    surface5 = font.render("abcdefghijklmnopqrstuvwxyz", True, 'white')
    try:
        surface6 = font.render("あいうえおアイウエオ漢字、日本語。", True, 'white')
    except Exception as e:
        surface6 = font.render(f"NIHONGO Exception: {str(e)}", True, 'white')

    screen.blit(surface1, [20,  20])
    screen.blit(surface2, [20,  70])
    screen.blit(surface3, [20, 120])
    screen.blit(surface4, [20, 170])
    screen.blit(surface5, [20, 220])
    screen.blit(surface6, [20, 270])

    pygame.display.update()

    if fontIndex < len(fontList) - 1:
        fontIndex += 1

    for event in pygame.event.get():
        if event.type == QUIT:
            done = True #exit
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE or event.key == K_q:
                done = True #exit

pygame.quit()

漢字文字列をレンダリングしたとき、font.renderで例外が発生するフォントがあったため、例外を拾って対処した。(環境依存だと思われる)

実際に日本語が表示できたフォントの例;

f_jp.png

  • 1行目:使用したフォント名(上の例では、"applegothic"
  • 2行目:数字を表示
  • 3行目:記号を表示
  • 4行目:アルファベット大文字を表示
  • 5行目:アルファベット小文字を表示
  • 6行目:日本語を表示

普通の英数字でさえ表示できないフォントもある

Symbol用のフォントだと思われます。

f_n.png

日本語が表示できないフォントの場合

6行目(の日本語)だけ文字化けします。

f_njp.png

上の2つの画像を見て分かることは、『表示できない文字はに化ける』ということです(ただし、の横幅や縦幅はフォントにより まちまち)。

表示可能なフォントを自動的に判断する方法を考察する

Pygameが文字をレンダリングするときの、ビットマップ(点の集合)を見ることで、ある程度の判断はできそうです。
しかし、あらかじめ どの文字がになるかは分かっていない為、のビットマップと比較することは、実質無理です。
そこで、レンダリングしたときのビットマップが、絶対に異なるであろう『2つの文字』のビットマップを比較して、完全一致した場合は『表示できない』と判断することを考える。

準備1. フォント名を指定を指定して、特定の文字のビットマップを得る

Pygameのsurfaceが実際にレンダリングするビットマップそのものです。
次のコードは、フォントリストで得たフォントを使い、サイズ30で文字'A'のビットマップを出力します。(全体が分かるように白黒反転している)
surface.get_at((x, y))で指定した座標(x, y)のpixel値が得られる)

d.py
import pygame
from pygame.locals import *

pygame.init()

def print_bitmap(font, char):
    surface = font.render(char, True, 'white', 'black')
    width, height = surface.get_size()
    for y in range(height):
        buf = ''
        for x in range(width):
            pixel = '*' if surface.get_at((x, y)) == (0, 0, 0) else ' '
            buf += pixel
        print(buf)

count = 1
for fontname in sorted(pygame.font.get_fonts()):
    try:
        font = pygame.font.SysFont(fontname, 30)
        print(f"{count}\t{fontname}")
        print_bitmap(font, "A")
        count += 1
    except:
        pass

pygame.quit()
実行例
$ python d.py
pygame 2.5.2 (SDL 2.28.3, Python 3.11.9)
Hello from the pygame community. https://www.pygame.org/contribute.html
1	
***************
*****     *****
*****     *****
****      *****
****       ****
****       ****
***        ****
***    *    ***
**     *    ***
**    **    ***
**           **
*            **
*             *
*    *****    *
     *****    *
    ******     
***************
***************
***************
***************
2	academyengravedlet
***********************
**********  ***********
**********   **********
*********    **********
*********     *********
*********  *  *********
********       ********
******** *  *  ********
*******  **     *******
*******  **  *  *******
******  ****     ******
******  ****     ******
*****  *****  *  ******
*****             *****
*****  ******  *  *****
****  ********     ****
***   ********  *  ****
***  **********     ***
**   **********  *  ***
*      *******   *    *
        *****          
***********************
***********************
***********************
***********************
***********************
***********************
***********************
***********************
***********************
***********************
***********************
***********************
***********************
***********************
***********************
3	albayan
**********************
**********************
**********************
**********************
**********************
**********************
**********************
**********************
*                    *
*                    *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*  ****************  *
*                    *
*                    *
**********************
**********************
**********************
**********************
**********************
**********************
**********************
**********************
**********************
**********************
**********************
**********************
**********************
**********************
**********************
(以下、省略)

準備2. 文字1と文字2のビットマップ同士を比較する

特定の文字のビットマップ得ることができたので、次に、文字1と別な文字2のビットマップと比較するコードを考える

  • 2つの文字のレンダリングサイズが不一致なら、ビットマップ内を比較せずとも、表示できる文字(=表示できるフォント)と判断する
    (この判断は本当は違う可能性があるが、表示できないフォントの場合は、同じサイズのが表示されることを前提とした)
     

  • 2つの文字のレンダリングサイズが同じなら、ビットマップ内のpixelを順に比較する。1pixelでも異なれば 表示できる文字(=表示できるフォント)と判断する。すべてのpixelが一致する場合のみ 表示不可
     

  • 判断に使用する2つの文字は'i''W'を、日本語の場合は'あ''漢'を用いる(明らかにレンダリングイメージが異なる文字なら何でもよい)
     

表示可能なフォントを自動判定するコード

前項の考察による ビットマップ比較コードは次の通り。

def isSame_bitmap(font, char):
    surface1 = font.render(char[0], True, 'white', 'black')
    surface2 = font.render(char[1], True, 'white', 'black')
    if surface1.get_size() != surface2.get_size(): return False
    width, height = surface1.get_size()
    for y in range(height):
        for x in range(width):
            if surface1.get_at((x, y)) != surface2.get_at((x, y)): return False
    return True

if isSame_bitmap(font, 'iW'): ・・・
if isSame_bitmap(font, 'あ漢'): ・・・

日本語が表示できるフォントだけに限定

既出のc.pyに、上記の判定コードを加えた結果、期待通り、
すべての文字が表示できるフォントだけになった。

c2.py.gif

フォント数は21種類に激減

コードは以下の通り。

c2.py
import pygame
from pygame.locals import *

def isSame_bitmap(font, char):
    surface1 = font.render(char[0], True, 'white', 'black')
    surface2 = font.render(char[1], True, 'white', 'black')
    if surface1.get_size() != surface2.get_size(): return False
    width, height = surface1.get_size()
    for y in range(height):
        for x in range(width):
            if surface1.get_at((x, y)) != surface2.get_at((x, y)): return False
    return True

pygame.init()
screen = pygame.display.set_mode((900, 400))

fontList = []
for fontname in sorted(pygame.font.get_fonts()):
    try:
        font = pygame.font.SysFont(fontname, 30)
        if isSame_bitmap(font, 'iW'): continue #⭐️
        if isSame_bitmap(font, 'あ漢'): continue
        fontList.append(fontname)
    except:
        pass

clock = pygame.time.Clock()

fontIndex, oldIndex = 0, -1
done = False
while not done:
    clock.tick(10)
    pygame.display.set_caption(f'[{fontIndex + 1}/{len(fontList)}] {fontList[fontIndex]}')
    if oldIndex != fontIndex:
        print(f'[{fontIndex + 1}] {fontList[fontIndex]}')
    oldIndex = fontIndex

    screen.fill('black')

    font = pygame.font.SysFont(fontList[fontIndex], 30)
    surface1 = font.render(fontList[fontIndex], True, 'white')
    surface2 = font.render("1234567890", True, 'white')
    surface3 = font.render("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", True, 'white')
    surface4 = font.render("ABCDEFGHIJKLMNOPQRSTUVWXYZ", True, 'white')
    surface5 = font.render("abcdefghijklmnopqrstuvwxyz", True, 'white')
    try:
        surface6 = font.render("あいうえおアイウエオ漢字、日本語。", True, 'white')
    except Exception as e:
        surface6 = font.render(f"NIHONGO Exception: {str(e)}", True, 'white')

    screen.blit(surface1, [20,  20])
    screen.blit(surface2, [20,  70])
    screen.blit(surface3, [20, 120])
    screen.blit(surface4, [20, 170])
    screen.blit(surface5, [20, 220])
    screen.blit(surface6, [20, 270])

    pygame.display.update()

    if fontIndex < len(fontList) - 1:
        fontIndex += 1

    for event in pygame.event.get():
        if event.type == QUIT:
            done = True #exit
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE or event.key == K_q:
                done = True #exit

pygame.quit()

漢字が表示できても英数字が表示されないフォントがある?

これは環境によると考えられるが、例えば、以下の2パターン。

  • 1; 出処不明なフォント

    除外するために、英字のチェック(if isSame_bitpattern(font, 'iW'))が必要。

J-ng1.png

  • 2; 特定の漢字が表示されないフォント

赤丸の位置の文字が表示されない

j-ng2.png

これを除外するためには、すべてのpixelが背景色であるフォントを除外するコードの追加が必要であるが、ビットマップをチェックする文字以外で文字化け(今回の現象が発生)する場合は 救えない。よって、最終的には目視で確認するしかない。

プラットホームによるフォント名の違い

Raspberry Pi OS 12.6(Bookworm)と Ubuntu24.02 の両方で、IPAフォントをインストールしたが、Pygameで取得するフォント名や種類に違いがあった。

$ sudo apt install -y fonts-ipaexfont fonts-ipafont

それぞれのプラットホームでIPAフォントをインストール後、既出のc2.pyを実行したところ、以下のようにフォント名と種類に差が出た。

プラットホーム スクショ
Ubuntu 24.02 ub1.png
ub2.png
ub3.png
Bookworm 12.6 bw1.png
bw2.png
bw3.png
bw4.png
bw5.png
bw6.png

同じコマンドでインストールしたフォントであるが、OSより扱いに差があるのか?

書体を見ると、Ubuntu の方はゴシック体のみで、明朝体が欠落していると思われる。理由は不明。

Ubuntu24.02 の fc-listコマンドで確認すると、8種類のフォントが確認できた。もしかして、Pygame側の問題??

$ fc-list | grep -i ipa
/usr/share/fonts/opentype/ipafont-mincho/ipam.ttf: IPA明朝,IPAMincho:style=Regular
/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf: IPAexゴシック,IPAexGothic:style=Regular
/usr/share/fonts/opentype/ipaexfont-mincho/ipaexm.ttf: IPAex明朝,IPAexMincho:style=Regular
/usr/share/fonts/opentype/ipafont-gothic/ipag.ttf: IPAゴシック,IPAGothic:style=Regular
/usr/share/fonts/truetype/fonts-japanese-mincho.ttf: IPAex明朝,IPAexMincho:style=Regular
/usr/share/fonts/truetype/fonts-japanese-gothic.ttf: IPAexゴシック,IPAexGothic:style=Regular
/usr/share/fonts/opentype/ipafont-mincho/ipamp.ttf: IPA P明朝,IPAPMincho,䥐䅐䵩湣桯:style=Regular
/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf: IPA Pゴシック,IPAPGothic,䥐䅐䝯瑨楣:style=Regular
$ 

プラットホームによって『同じフォントであっても』フォント名が異なると、扱いづらい・・・
Python標準で Bookworm(Debian) と Ubuntu の実行環境を判断する方法があるのか??




最後に、
 
フォント名の一覧を出力するコードを提示して、今回の記事を締め括ります。

Pygame font List

フォントごとに次のコメントが付きます。

・can not draw Japanese.
日本語が表示できないフォント(ビットマップ判定)
・is not available for font.render.
日本語が表示できないフォント(font.render不可)
・can not draw alphabet
英数字が表示できないフォント(ビットマップ判定)
・is not available for font.SysFont.
使用できないフォント(font.SysFont不可)
・コメントなし
英数字も日本語も表示できるフォント
実行例
% python pygame_fonts.py
pygame 2.5.2 (SDL 2.28.3, Python 3.11.9)
Hello from the pygame community. https://www.pygame.org/contribute.html
1	                             	can not draw Japanese.
2	academyengravedlet           	can not draw Japanese.
3	albayan                      	can not draw alphabet.
4	albayanpua                   	can not draw alphabet.
5	alnile                       	can not draw alphabet.
6	alnilepua                    	can not draw alphabet.
7	altarikh                     	can not draw alphabet.
8	altarikhpua                  	can not draw alphabet.
9	americantypewriter           	can not draw Japanese.
10	andalemono                   	can not draw Japanese.
11	applebraille                 	can not draw alphabet.
12	applechancery                	can not draw Japanese.
13	applecoloremoji              	can not draw alphabet.
14	applecoloremojiui            	can not draw alphabet.
15	applegothic
16	applemyungjo
17	applesdgothicneo
18	applesdgothicneoi
(以下、省略)
pygame_fonts.py
import pygame
from pygame.locals import *

pygame.init()

def isSame_bitmap(font, char):
    surface1 = font.render(char[0], True, 'white', 'black')
    surface2 = font.render(char[1], True, 'white', 'black')
    if surface1.get_size() != surface2.get_size(): return False
    width, height = surface1.get_size()
    for y in range(height):
        for x in range(width):
            if surface1.get_at((x, y)) != surface2.get_at((x, y)): return False
    return True

maxFontLength = 0
fontList = []
for index, fontname in enumerate(sorted(pygame.font.get_fonts())):
    maxFontLength = max(maxFontLength, len(fontname))
    try:
        font = pygame.font.SysFont(fontname, 30)
        fontList.append((index+1, fontname, True))
    except:
        fontList.append((index+1, fontname, False))

for num, fontname, isAvailable in fontList:
    print(f"{num}\t{fontname}{' '*(maxFontLength-len(fontname))}\t", end='')
    if not isAvailable:
        print("is not available for font.SysFont.")
        continue

    font = pygame.font.SysFont(fontname, 30)
    if isSame_bitmap(font, 'iW'):
        print("can not draw alphabet.")
        continue

    try:
        surface6 = font.render("あいうえおアイウエオ漢字、日本語。", True, 'white')
        if isSame_bitmap(font, 'あ漢'):
            print("can not draw Japanese.")
            continue

        print()
    except:
        print("is not available for font.render.")

pygame.quit()

Pygame font viewer

おまけで、一つづつのフォントをゆっくり確認できるコードも提示します。

以下のコードは、矢印キーを押すことで、次/前のフォントを表示する。矢印キーが押されるまでは動かないので、表示内容をゆっくり確認することができる。
(長押しすると、キーリピートにより次々にフォントが切り替わる)

もし、英数字や日本語が表示できるフォントに限る場合は、23行目と24行目のif文を必要により有効にしてもらいたい。

pygame_font_viewer.py
import pygame
from pygame.locals import *

pygame.init()
pygame.key.set_repeat(500, 100)
screen = pygame.display.set_mode((900, 400))

def isSame_bitmap(font, char):
    surface1 = font.render(char[0], True, 'white', 'black')
    surface2 = font.render(char[1], True, 'white', 'black')
    if surface1.get_size() != surface2.get_size(): return False
    width, height = surface1.get_size()
    for y in range(height):
        for x in range(width):
            if surface1.get_at((x, y)) != surface2.get_at((x, y)): return False
    return True

fontList = []
for fontname in sorted(pygame.font.get_fonts()):
    try:
        font = pygame.font.SysFont(fontname, 30)
        #skip undrawable font
        #if isSame_bitmap(font, 'iW'): continue # ⭐️
        #if isSame_bitmap(font, 'あ漢'): continue # ⭐️
        fontList.append(fontname)
    except:
        pass

fontIndex = 0
done = False
while not done:
    fontname = fontList[fontIndex]
    pygame.display.set_caption(f'[{fontIndex + 1}/{len(fontList)}] {fontname}')

    screen.fill('black')

    font = pygame.font.SysFont(fontname, 30)
    surface1 = font.render(fontname, True, 'white')
    surface2 = font.render("1234567890", True, 'white')
    surface3 = font.render("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", True, 'white')
    surface4 = font.render("ABCDEFGHIJKLMNOPQRSTUVWXYZ", True, 'white')
    surface5 = font.render("abcdefghijklmnopqrstuvwxyz", True, 'white')
    try:
        surface6 = font.render("あいうえおアイウエオ漢字、日本語。", True, 'white')
    except Exception as e:
        surface6 = font.render(f"NIHONGO Exception: {str(e)}", True, 'white')

    screen.blit(surface1, [20,  20])
    screen.blit(surface2, [20,  70])
    screen.blit(surface3, [20, 120])
    screen.blit(surface4, [20, 170])
    screen.blit(surface5, [20, 220])
    screen.blit(surface6, [20, 270])

    pygame.display.update()

    for event in pygame.event.get():
        if event.type == QUIT:
            done = True #exit
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE or event.key == K_q:
                done = True #exit
            if (event.key == K_UP or event.key == K_LEFT) and fontIndex > 0:
                fontIndex -= 1
            if (event.key == K_DOWN or event.key == K_RIGHT) and fontIndex < len(fontList) - 1:
                fontIndex += 1
        if event.type == KEYUP:
            if event.key == K_HOME or event.key == K_PAGEUP:
                fontIndex = 0
            if event.key == K_END or event.key == K_PAGEDOWN:
                fontIndex = len(fontList) - 1
            if event.key == K_SPACE:
                print(f'[{fontIndex + 1}] {fontname}')

pygame.quit()

何かの役に立てれば嬉しい。

以上

2
2
0

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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?