ImageFont Module

doc

https://pillow.readthedocs.io/en/latest/reference/ImageFont.html, http://effbot.org/imagingbook/imagefont.htm

Using TrueType (or OpenType) Fonts

The official documents says:

Starting with version 1.1.4, PIL can be configured to support TrueType and OpenType fonts (as well as other font formats supported by the FreeType library). For earlier versions, TrueType support is only available as part of the imToolkit package

If you want to use TrueType (or OpenType) fonts, normally, the only thing what you need is to know truetype factory function.

truetype factory function

doc

https://pillow.readthedocs.io/en/latest/reference/ImageFont.html#PIL.ImageFont.truetype, http://effbot.org/imagingbook/imagefont.htm#tag-ImageFont.truetype

The example for the font having various faces:

# This example is for Windows.
from PIL import Image, ImageDraw, ImageFont

pangram = "Pack my box with five dozen liquor jugs."

fonts = [
    ImageFont.truetype('gulim', 28, index=i)
    for i in range(4)]
texts = [
    str(font.getname()) + ": " + pangram
    for font in fonts
    ]
#
size = [0, 0]
for s in [fonts[i].getsize(txt) for txt in texts]:
    size = [max(size[0], s[0]), size[1] + s[1]]
#
img = Image.new("L", size, 255)
draw = ImageDraw.Draw(img)
y = 0
for font, txt in zip(fonts, texts):
    draw.text((0, y), txt, font=font, fill=0)
    y += font.getsize(txt)[1]
#img.show()
img.save("result/ImageFont_gulim_01.png")
del draw

ImageFont.res1

This example draws a character using the Microsoft Symbol font, which uses the “symb” encoding and characters in the range 0xF000 to 0xF0FF:

# -*- coding: utf-8 -*-
# This example is for Windows.
#
# This example draws a character using the Microsoft Symbol font,
# which uses the "symb" encoding and characters
# in the range 0xF000 to 0xF0FF.
#
from __future__ import unicode_literals
from textwrap import TextWrapper
from PIL import Image, ImageDraw, ImageFont
try:
    unichr(0)
except:
    unichr = chr
txtwrap = TextWrapper(width=32).fill

font = ImageFont.truetype("symbol.ttf", 35, encoding="symb")
txt = txtwrap("".join([unichr(0xF000 + i) for i in range(0x100)]))
img = Image.new("L", (900, 360), 255)
draw = ImageDraw.Draw(img)
draw.multiline_text((0, 0), txt, font=font)
#img.show()
img.save("result/ImageFont_symbol_01.png")
del draw

ImageFont.res2

The other practical examples are found at ImageDraw.text, etc.

class FreeTypeFont

Note

Note that this class is desired not to create explicitly. You should use truetype factory function to create this object.

When you want to use text() (or multiline_text()), you might need to load fonts, and if you want to use TrueType or OpenType fonts, you will have to call truetype. Because the truetype factory function requests the name of the font file, so you may not know what the font file you want. You can show the family and style from the font file like this:

>>> from glob import iglob
>>> from PIL import ImageFont
>>>
>>> ttf = ImageFont.truetype(font="c:/Windows/Fonts/cour.ttf")  # returns new FreeTypeFont object
>>> ttf.getname()  # returns `family' and `type'.
('Courier New', 'Regular')
>>>
>>> for fn in iglob("c:/Windows/Fonts/*.*"):
...     try:
...         ttf = ImageFont.truetype(font=fn)
...         print("FT {}: {}".format(fn, ttf.getname()))
...     except Exception as e:
...         print("XX {}: {}".format(fn, e))
...
XX c:/Windows/Fonts\8514fix.fon: invalid pixel size
XX c:/Windows/Fonts\8514fixe.fon: invalid pixel size
   -- (snip) --
FT c:/Windows/Fonts\ahronbd.ttf: ('Aharoni', 'Bold')
FT c:/Windows/Fonts\andlso.ttf: ('Andalus', 'Regular')
   -- (snip) --
FT c:/Windows/Fonts\CENTURY.TTF: ('Century', 'Regular')
XX c:/Windows/Fonts\cga40737.fon: invalid pixel size
   -- (snip) --
FT c:/Windows/Fonts\consola.ttf: ('Consolas', 'Regular')
FT c:/Windows/Fonts\consolab.ttf: ('Consolas', 'Bold')
FT c:/Windows/Fonts\consolai.ttf: ('Consolas', 'Italic')
FT c:/Windows/Fonts\consolaz.ttf: ('Consolas', 'Bold Italic')
   -- (snip) --

You might want to know available faces, you can show up these like this:

>>> from glob import iglob
>>> from fnmatch import fnmatch
>>> from PIL import ImageFont
>>>
>>> for fn in iglob("c:/Windows/Fonts/*.tt*"):
...     if not fnmatch(fn, "*.tt[cf]"):
...         continue
...     try:
...         for i in range(5):
...             ttf = ImageFont.truetype(font=fn, index=i)
...             print("{} (face={}): {}".format(fn, i, ttf.getname()))
...     except IOError as e:
...         pass
...
c:/Windows/Fonts\ahronbd.ttf (face=0): ('Aharoni', 'Bold')
c:/Windows/Fonts\andlso.ttf (face=0): ('Andalus', 'Regular')
    --- (snip) ---
c:/Windows/Fonts\batang.ttc (face=0): ('Batang', 'Regular')
c:/Windows/Fonts\batang.ttc (face=1): ('BatangChe', 'Regular')
c:/Windows/Fonts\batang.ttc (face=2): ('Gungsuh', 'Regular')
c:/Windows/Fonts\batang.ttc (face=3): ('GungsuhChe', 'Regular')
    --- (snip) ---

Though the font_variant method is not visible in the official documents, but it might be sometime useful:

>>> from PIL import Image, ImageDraw, ImageFont
>>>
>>> gulim_face0 = ImageFont.truetype("gulim", 16)
>>> gulim_face0.getsize("aaa")
(28, 14)
>>> gulim_face0.font_variant(index=1).getname()
('GulimChe', 'Regular')
>>> gulim_face0.font_variant(index=2).getname()
('Dotum', 'Regular')
>>> gulim_face0.font_variant(index=3).getname()
('DotumChe', 'Regular')
>>>
>>> # or...
>>> gulim_face0.font_variant(size=32).getsize("aaa")
(54, 28)

You can also get the metrics of the font object, but normally you don’t need to care about it. :

>>> from PIL import ImageFont
>>> ttf = ImageFont.truetype("CENTURY.TTF")
>>> ttf.getmetrics()  # returns (ascent, descent)
(10, 2)

The getsize method of this class can calculate the size of given text:

>>> from PIL import Image, ImageDraw, ImageFont
>>>
>>> text = "Jackdaws love my big sphinx of quartz."
>>>
>>> ttf = ImageFont.truetype("CENTURY.TTF", 64)
>>> ttf.getsize(text)
(1163, 75)

But note that you can use ImageDraw.textsize and ImageDraw.multiline_textsize. Especially for the multiline text, it is better to use ImageDraw.multiline_textsize rather than getsize, because the getsize method of this class is not what you want.:

>>> from PIL import Image, ImageDraw, ImageFont
>>>
>>> text = """\
... Pack my box with five dozen liquor jugs.
... Jackdaws love my big sphinx of quartz.
... The five boxing wizards jump quickly.
... How vexingly quick daft zebras jump!
... Bright vixens jump; dozy fowl quack.
... Sphinx of black quartz, judge my vow.
... """
>>>
>>> ttf = ImageFont.truetype("CENTURY.TTF", 64)
>>> ttf.getsize(text)
(7088, 75)
>>> img = Image.new("L", (1200, 500))
>>> dctx = ImageDraw.Draw(img)
>>> dctx.multiline_textsize(text, font=ttf)
(1191, 450)

Though the getoffset method is undocumented, but it should be easy to know about the meaning of the getoffset method by the following demonstration:

# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw, ImageFont

iw, ih = 500, 120
text = "although"
img = Image.new("RGB", (iw, ih), color="white")
dctx = ImageDraw.Draw(img)

# courbi.ttf: Courier New Bold Italic (on Microsoft Windows)
ttf = ImageFont.truetype("courbi.ttf", 100)
w, h = ttf.getsize(text)
off_x, off_y = ttf.getoffset(text)
mx, my = (iw - w) // 2, (ih - h) // 2

dctx.line(((mx + off_x, my + off_y), (iw - mx, my + off_y)),
          fill="blue")

dctx.rectangle(((mx, my), (iw - mx, ih - my)), outline="red")
dctx.text((mx, my), text, font=ttf, fill="black")
#img.show()
img.save("result/ImageFont_getoffset_01.png")
del dctx

ImageFont.getoffset.res1

The getmask method returns the rectangle Image object having the size (font.getsize()[0] - font.getoffset()[0], font.getsize()[1] - font.getoffset()[1], but you ordinally don’t need this method, furthermore this might not be what you want if the text has multiline. This method is internally used by ImageDraw.text and ImageDraw.multiline_text.

Enumerating truetype fonts on Windows

Of course because you know the location of system fonts (“c:/Windows/Fonts”), so you can:

>>> from glob import iglob
>>>
>>> for fn in iglob("c:/Windows/Fonts/*.*"):
...    print(fn)

but officialy, querying from the registry seems to be the right way:

This is the case in my environment. It will be different from your environment.
>>> try:
...     import winreg
... except ImportError:
...     import _winreg as winreg
...
>>> #
>>> key = None
>>> for sk in [
...     r'Software\Microsoft\Windows NT\CurrentVersion\Fonts',
...     r'Software\Microsoft\Windows\CurrentVersion\Fonts']:
...     try:
...         key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, sk)
...         break
...     except EnvironmentError:
...         pass
...
>>> #
>>> i = 0
>>> while True:
...     try:
...         value_name, value_data, data_type = winreg.EnumValue(key, i)
...         print("{}: '{}'".format(value_data, value_name))
...         i += 1
...     except OSError as e:  # actually this means "no more data"
...         break
...
arial.ttf: 'Arial (TrueType)'
ariali.ttf: 'Arial Italic (TrueType)'
arialbd.ttf: 'Arial Bold (TrueType)'
arialbi.ttf: 'Arial Bold Italic (TrueType)'
batang.ttc: 'Batang & BatangChe & Gungsuh & GungsuhChe (TrueType)'
cour.ttf: 'Courier New (TrueType)'
couri.ttf: 'Courier New Italic (TrueType)'
courbd.ttf: 'Courier New Bold (TrueType)'
courbi.ttf: 'Courier New Bold Italic (TrueType)'
   ... (snip) ...
gulim.ttc: 'Gulim & GulimChe & Dotum & DotumChe (TrueType)'
   ... (snip) ...
mangalb.ttf: 'Mangal Bold (TrueType)'
meiryo.ttc: 'Meiryo & Meiryo Italic & Meiryo UI & Meiryo UI Italic (TrueType)'
meiryob.ttc: 'Meiryo Bold & Meiryo Bold Italic & Meiryo UI Bold & Meiryo UI Bold Italic (TrueType)'
himalaya.ttf: 'Microsoft Himalaya (TrueType)'
   ... (snip) ...
webdings.ttf: 'Webdings (TrueType)'
COURE.FON: 'Courier 10,12,15'
SERIFE.FON: 'MS Serif 8,10,12,14,18,24'
SSERIFE.FON: 'MS Sans Serif 8,10,12,14,18,24'
JSMALLE.FON: 'Small Fonts'
SMALLF.FON: 'Small Fonts (120)'
calibrili.ttf: 'Calibri Light Italic (TrueType)'
   ... (snip) ...
ARBERKLEY.ttf: 'AR BERKLEY (OpenType)'
ARBLANCA.ttf: 'AR BLANCA (OpenType)'
ARBONNIE.ttf: 'AR BONNIE (OpenType)'
ARCARTER.ttf: 'AR CARTER (OpenType)'
ARCENA.ttf: 'AR CENA (OpenType)'
   ... (snip) ...
Swkeys1.ttf: 'SWGamekeys MT (TrueType)'

The information obtained from the registry is not the full path of the font file, but it is no problem because the truetype factory does not require the full path:

>>> # -*- coding: utf-8 -*-
>>> from PIL import ImageFont
>>> try:
...     import winreg
... except ImportError:
...     import _winreg as winreg
...
>>> #
>>> key = None
>>> for sk in [
...     r'Software\Microsoft\Windows NT\CurrentVersion\Fonts',
...     r'Software\Microsoft\Windows\CurrentVersion\Fonts']:
...     try:
...         key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, sk)
...         break
...     except EnvironmentError:
...         pass
...
>>> #
>>> path, _ = winreg.QueryValueEx(
...     key, 'MS Gothic & MS PGothic & MS UI Gothic (TrueType)')
>>> font = ImageFont.truetype(path, 32)
>>> font
<PIL.ImageFont.FreeTypeFont object at 0x00000000039C4278>
>>> font.getname()
('MS Gothic', 'Regular')
>>> font.font_variant(index=0).getname()
('MS Gothic', 'Regular')
>>> font.font_variant(index=1).getname()
('MS PGothic', 'Regular')
>>> font.font_variant(index=2).getname()
('MS UI Gothic', 'Regular')
>>> #
>>> # "Meiryo & Meiryo Italic & Meiryo UI & Meiryo UI Italic (TrueType)"
>>> font = ImageFont.truetype("meiryo.ttc", 32)
>>> font.font_variant(index=0).getname()
('Meiryo', 'Regular')
>>> font.font_variant(index=1).getname()
('Meiryo', 'Italic')
>>> font.font_variant(index=2).getname()
('Meiryo UI', 'Regular')
>>> font.font_variant(index=3).getname()
('Meiryo UI', 'Italic')

To try out all the fonts available in your environment, you can do it by browsing c:/Windows/Fonts with Windows Explorer. However, that method is insufficient as the information required by the ImageFont interface, especially we can not know the file name. If you are troubled with this, you can try it out as follows:

enum_all_ttfonts_to_html.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import sys
import re

from PIL import Image, ImageDraw, ImageFont


def _enum_windows_fonts(
    name_filter=lambda n: re.search(r"\(.*?Type\)$", n),
    name_conv=lambda s: list(map(lambda x: x.strip(), re.match(r"^(.*)\s?\(.*?Type\)$", s).group(1).split("&")))):
    try:
        import winreg
    except ImportError:
        import _winreg as winreg
    #
    key = None
    for sk in [
        r'Software\Microsoft\Windows NT\CurrentVersion\Fonts',
        r'Software\Microsoft\Windows\CurrentVersion\Fonts']:
        try:
            key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, sk)
            break
        except EnvironmentError:
            pass
    #
    i = 0
    while True:
        try:
            value_name, value_data, data_type = winreg.EnumValue(key, i)
            if hasattr(value_name, "decode"):
                try:
                    value_name = value_name.decode()
                except UnicodeDecodeError:
                    value_name = value_name.decode("mbcs")
            if not name_filter or name_filter(value_name):
                if name_conv:
                    value_name = name_conv(value_name)
                yield value_data, value_name  # filename, name
            i += 1
        except OSError as e:  # actually this means "no more data"
            break

#
def _render_all_truetypes(text, encoding=""):  # encoding: "symb", etc.
    tabitems = []
    for i, (fn, names) in enumerate(_enum_windows_fonts()):
        try:
            face0 = ImageFont.truetype(fn, 28, encoding=encoding)
        except IOError:
            continue
        for j in range(len(names)):
            font = face0.font_variant(index=j, encoding=encoding)
            img = Image.new("L", (2, 2))
            dctx = ImageDraw.Draw(img)
            w, h = dctx.multiline_textsize(text, font=font)
            del dctx
            del img
            #
            img = Image.new("L", (w, h + 32), "white")
            dctx = ImageDraw.Draw(img)
            dctx.multiline_text((0, 8), text, font=font)
            outname = "font_%02d_%d.png" % (i, j)
            tabitems.append((fn, names[j], font.getname(), outname))
            img.save(outname)
            del dctx
    return tabitems

#
def _render_all_truetypes_to_html(text, encoding=""):  # encoding: "symb", etc.
    tabitems = _render_all_truetypes(text, encoding)
    trs = []
    for fn, dispname, (name, style), pngpath in tabitems:
        trs.append("""\
<tr>
    <td>{fn}</td>
    <td>{dispname}</td>
    <td>{name}</td>
    <td>{style}</td>
    <td><img src="{pngpath}" /></td>
</tr>""".format(**locals()))
    #
    print("""\
<html>
<body>
<table border="1">
{}
</table>
</body>
</html>
""".format("\n".join(trs)))


if __name__ == '__main__':
    _render_all_truetypes_to_html(
        "\n".join(map(lambda s: s.rstrip(), sys.stdin.readlines())),
        encoding="")
[me@host: ~]$ echo The quick brown fox jumps over a lazy dog. | \
> python enum_all_ttfonts_to_html.py > result.html

Here’s a video of the script execution result html:

Watch on youtube.com

Note

If the result html is pasted here as it is, it is quite big. I hated this and presented it as a video. Of course, running the above script will not create a video. For how to make html a video, see Capturing webpage as image and converting it to video

Enumerating truetype fonts in some directory

Whether you are a Windows user or a Unix user, you may want to use third-party fonts instead of system-standard fonts. In such cases, it may not have been “installed” in a system standard way. In other words, you can’t query the registry on Windows, and you can’t query on other platforms using standard ways.

Even in this case, you can easily get information (if you know where the truetype font is located) using the functions you have seen above:

enum_all_ttfonts_to_html_part2.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import os, sys
from PIL import Image, ImageDraw, ImageFont


def _enum_font_variant(f):
    result = []
    i = 0
    while True:
        try:
            result.append(f.font_variant(index=i).getname()[0])
        except (OSError, IOError):
            # OSError will occur in Python 3.x, and
            # IOError will occur in Python 2.7...
            break
        i += 1
    return result


def _enum_fonts_in_dir(fontsdir):
    from fnmatch import fnmatch
    for root, dirs, files in os.walk(fontsdir):
        for fn in files:
            if not fnmatch(fn, "*.tt[fc]"):
                continue
            fonpath = os.path.join(root, fn)
            fon = ImageFont.truetype(font=fonpath)
            yield fonpath, _enum_font_variant(fon)

#
def _render_all_truetypes(fontsdir, text, encoding=""):  # encoding: "symb", etc.
    tabitems = []
    for i, (fn, names) in enumerate(_enum_fonts_in_dir(fontsdir)):
        try:
            face0 = ImageFont.truetype(fn, 24, encoding=encoding)
        except IOError:
            continue
        for j in range(len(names)):
            font = face0.font_variant(index=j, encoding=encoding)
            img = Image.new("L", (2, 2))
            dctx = ImageDraw.Draw(img)
            w, h = dctx.multiline_textsize(text, font=font)
            del dctx
            del img
            #
            img = Image.new("L", (w, h + 32), "white")
            dctx = ImageDraw.Draw(img)
            dctx.multiline_text((0, 8), text, font=font)
            outname = "font_%02d_%d.png" % (i, j)
            tabitems.append((
                    os.path.relpath(fn, fontsdir).replace("\\", "/"),
                    names[j], font.getname(), outname))
            img.save(outname)
            del dctx
    return tabitems

#
def _render_all_truetypes_to_html(
    fontsdir,
    text, encoding=""):  # encoding: "symb", etc.
    tabitems = _render_all_truetypes(fontsdir, text, encoding)

    trs = []
    for fn, dispname, (name, style), pngpath in tabitems:
        trs.append("""\
<tr>
    <td style="font-size: x-small">{fn}</td>
    <td style="font-size: small">{dispname}</td>
    <td style="font-size: small">{name}</td>
    <td style="font-size: small">{style}</td>
    <td><img src="{pngpath}" /></td>
</tr>""".format(**locals()))
    #
    print("""\
<html>
<body>
<h1>{}</h1>
<table border="1">
{}
</table>
</body>
</html>
""".format(fontsdir, "\n".join(trs)))


if __name__ == '__main__':
    _render_all_truetypes_to_html(
        sys.argv[1],  # for example "c:/texlive/2015/texmf-dist/fonts".
        "\n".join(map(lambda s: s.rstrip(), sys.stdin.readlines())),
        encoding="")
[me@host: ~]$ echo The quick brown fox jumps over a lazy dog. | \
> python enum_all_ttfonts_to_html_part2.py \
> c:/texlive/2015/texmf-dist/fonts > result.html

Using Bitmap Fonts

A long time ago, the bitmap font was the world. All the fonts were based on bitmap. That is, those were fixed sized image, and were not scalable.

Eventually a storm of a scalable font came and the world changed. No more people might need the bitmap fonts.

The only way to deal with the fonts for PIL was also to use bitmap fonts. This section explains that traditional way how to deal with the fonts in PIL. But if you have no special reason not to use scalable font, you should wonder why not to use the truetype.

load_default

You can always use the “better than nothing” font, it is surely better than nothing:

from PIL import Image, ImageFont, ImageDraw

text = "better than nothing"
font = ImageFont.load_default()
img = Image.new("L", font.getsize(text), 255)
dctx = ImageDraw.Draw(img)
dctx.text((0, 0), text, font=font)
del dctx
img = img.resize((img.width * 8, img.height * 8))
#img.show()
img.save("resut/ImageFont_load_default_01.png")

ImageFont_load_default.res1

pilfont utility script

If you are not satisfied with the font provided by load_default, you might have to setup the PIL fonts to your system.

Of course, the first thing you need to do is to get the BDF (or PCF) font descriptors (X window font formats) if you do not have those. However, many of recent systems are considered to have a tendency not to install them by default, in addition, those systems’s package managers might even not manage bitmap fonts. If so, it is a good opportunity to ask yourself if you really have to do this.

Unless using truetype, PIL uses its own font file format to store bitmap fonts. So, the second thing you need to do is to compile the BDF (or PCF) font descriptors (X window font formats) by using pilfont utility script:

me@host: ~$ pilfont.py
PILFONT 0.4 -- PIL font compiler.

Usage: pilfont fontfiles...

Convert given font files to the PIL raster font format.
This version of pilfont supports X BDF and PCF fonts.
me@host: ~$ ls
FuBar-Bold.bdf
FuBar-BoldOblique.bdf
FuBar-ExtraLight.bdf
FuBar-Oblique.bdf
FuBar.bdf
me@host: ~$ pilfont.py *.bdf
FuBar-Bold.bdf... OK
FuBar-BoldOblique.bdf... OK
FuBar-ExtraLight.bdf... OK
FuBar-Oblique.bdf... OK
FuBar.bdf... OK
me@host: ~$ ls
FuBar-Bold.bdf
FuBar-Bold.pbm
FuBar-Bold.pil
FuBar-BoldOblique.bdf
FuBar-BoldOblique.pbm
FuBar-BoldOblique.pil
FuBar-ExtraLight.bdf
FuBar-ExtraLight.pbm
FuBar-ExtraLight.pil
FuBar-Oblique.bdf
FuBar-Oblique.pbm
FuBar-Oblique.pil
FuBar.bdf
FuBar.pbm
FuBar.pil

Now you can place them in the right place. The meaning of “the right place” is where the load or load_path function can refer.

load, load_path

Once you place the PIL fonts in the right place, you can:

from PIL import Image, ImageFont, ImageDraw

text = "Are you hangry?"
font = ImageFont.load("/path/to/FuBar-Bold.pil")
#font = ImageFont.load_path("FuBar-Bold.pil")  # searches for a bitmap font along the Python path.
img = Image.new("L", font.getsize(text), 255)
dctx = ImageDraw.Draw(img)
dctx.text((0, 0), text, font=font)
del dctx
img = img.resize((img.width * 8, img.height * 8))
img.show()

class ImageFont

To create this object, use load, load_path, or load_default.

This class exposes only two methods, getsize and getmask. These two methods are the same as those in FreeTypeFont. See FreeTypeFont.