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 :ref:`truetype factory function `. .. _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 .. |ImageFont.res1| image:: _static/exams/result/ImageFont_gulim_01.png .. |ImageFont.res2| image:: _static/exams/result/ImageFont_symbol_01.png The example for the font having various faces: .. list-table:: :widths: 10 30 * - .. literalinclude:: _static/exams/ImageFont_truetype_01.py - | | |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: .. list-table:: :widths: 10 30 * - .. literalinclude:: _static/exams/ImageFont_truetype_02.py - | | |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 :ref:`text() ` (or :ref:`multiline_text() `), you might need to load fonts, and if you want to use TrueType or OpenType fonts, you will have to call :ref:`truetype `. Because the :ref:`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: .. code-block:: pycon >>> 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: .. code-block:: pycon >>> 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: .. code-block:: pycon >>> 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. : .. code-block:: pycon >>> 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: .. code-block:: pycon >>> 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 :ref:`ImageDraw.textsize ` and :ref:`ImageDraw.multiline_textsize `. Especially for the multiline text, it is better to use :ref:`ImageDraw.multiline_textsize ` rather than *getsize*, because the *getsize* method of this class is not what you want.: .. code-block:: pycon >>> 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: .. |ImageFont.getoffset.res1| image:: _static/exams/result/ImageFont_getoffset_01.png .. list-table:: :widths: 10 30 * - .. literalinclude:: _static/exams/ImageFont_FreeTypeFont_getoffset.py - | | |ImageFont.getoffset.res1| The *getmask* method returns the rectangle Image object having the size :code:`(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 :ref:`ImageDraw.text ` and :ref:`ImageDraw.multiline_text `. Enumerating truetype fonts on Windows ===================================== Of course because you know the location of system fonts ("c:/Windows/Fonts"), so you can: .. code-block:: pycon >>> 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: .. code-block:: pycon :caption: 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: .. code-block:: pycon >>> # -*- 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 >>> 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 :code:`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: .. code-block:: python :caption: 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("""\ {fn} {dispname} {name} {style} """.format(**locals())) # print("""\ {}
""".format("\n".join(trs))) if __name__ == '__main__': _render_all_truetypes_to_html( "\n".join(map(lambda s: s.rstrip(), sys.stdin.readlines())), encoding="") .. code-block:: console [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: .. my_youtube:: jp7HFHhDyyg .. 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 <../../ffmpeg/misc/showing_huge_stillimage_as_video.html#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: .. code-block:: python :caption: 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("""\ {fn} {dispname} {name} {style} """.format(**locals())) # print("""\

{}

{}
""".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="") .. code-block:: console [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 ============ .. |ImageFont_load_default.res1| image:: _static/exams/result/ImageFont_load_default_01.png You can always use the "better than nothing" font, it is surely better than nothing: .. code-block:: python 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 :ref:`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 :code:`pilfont` utility script: .. code-block:: console 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 :code:`load` or :code:`load_path` function can refer. load, load_path =============== Once you place the PIL fonts in the right place, you can: .. code-block:: python 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 `_.