Basj
Basj

Reputation: 46463

Choosing a PIL.ImageFont by font name rather than filename, and cross-platform font

ImageFont.truetype requires a filename to work, such as:

font = ImageFont.truetype("ariblk.ttf")  # Arial Black

Is there a way with PIL, to load a font by name, rather than filename?

Context: I would like to load a bold (with heavy weight) sans-serif font, that would work on any platform Windows, Linux, Mac.

I don't think ImageFont.truetype("ariblk.ttf") will work cross-platform, is it possible to load it with ImageFont.truetype("Arial Black") or, better, ImageFont.truetype("sans-serif;bold") that would work on all platforms?

Upvotes: 6

Views: 4800

Answers (3)

OysterShucker
OysterShucker

Reputation: 5531

You can use SimPIL-Font.

Basic Usage
from simpilfont import SimPILFont, FONTMAP

#load .ttf fonts from a directory and all sub-directories
FONTMAP(fontdir='path/to/fonts') 

text = "Hello World"

#load/configure font
sf  = SimPILFont('DejaVu Sans 16 condensed bold oblique')

#get ImageFont
ttf = sf.font

#measure text
x,y,w,h = sf.bbox(text)

img  = Image.new("RGB", (w-x, h-y), color="black")
dctx = ImageDraw.Draw(img)

dctx.text((-x, -y), text, font=ttf, fill="white")

img.show()
del dctx

Upvotes: 0

Gabriel Ing
Gabriel Ing

Reputation: 21

The top answer by HansHirse is fantastic but just to add to it for anyone still looking, you can also use matplotlib.font_manager to find specific fonts:

from matplotlib import font_manager

#print all available fonts 
print(font_manager.get_font_names())


#Find the path for a specific font:
file = font_manager.findfont('Helvetica Neue')

#Load the font to pillow
font = ImageFont.truetype(file, 20)

#draw text onto image using pillow:
draw.text((20, 20), 'Hello World', font=font, fill=(0, 0, 0))

If you are particularly worried about fonts not being available on other systems but would like to keep your default, you can combine these in a try...except... cause:

try:
    file = font_manager.findfont('Helvetica Neue')
    font = ImageFont.truetype(file, fontsize)
except:
    font_search = font_manager.FontProperties(family='sans-serif', weight='normal')
    file = font_manager.findfont(font_search)
    font = ImageFont.truetype(file, fontsize)

This way you can be confident you either get the default font or a similar font if it is not being available.

Upvotes: 2

HansHirse
HansHirse

Reputation: 18895

Looking at the documentation of Pillow's ImageFont module, there's no such an option, no.

A handy workaround might be to use Matplotlib's font_manager module for that: A module for finding, managing, and using fonts across platforms. Using the FontProperties and findfont, you should get the a valid path to a font with the given properties, which you can then use in the common ImageFont.truetype call.

Here's a small example, which runs perfectly fine on my Windows machine. Unfortunately, I don't have any other OS nearby to test.

from matplotlib import font_manager
from PIL import Image, ImageDraw, ImageFont

font = font_manager.FontProperties(family='sans-serif', weight='bold')
file = font_manager.findfont(font)
print(file)

img = Image.new('RGB', (400, 300), (255, 255, 255))
draw = ImageDraw.Draw(img)

font = ImageFont.truetype(file, 48)
draw.text((20, 20), 'Hello World', font=font, fill=(255, 0, 0))

img.save('test.png')

The print output:

...\Lib\site-packages\matplotlib\mpl-data\fonts\ttf\DejaVuSans-Bold.ttf

The image output:

Output

----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.16299-SP0
Python:        3.9.1
Matplotlib:    3.3.4
Pillow:        8.1.0
----------------------------------------

Upvotes: 17

Related Questions