Pillow icon indicating copy to clipboard operation
Pillow copied to clipboard

Support setting a default font

Open guoquan opened this issue 3 years ago • 6 comments

What did you do?

Was printing CJK characters to an image but got an AttributeError. No doubt the default font wasn't supporting such characters.

What did you expect to happen?

Understand I can use Unicode-friendly fonts everytime I do text(). However, can I somehow change the default font so that I won't need to change everywhere in my program text() with a font?

What actually happened?

Checking the docs there's PIL.ImageFont.load_default() to

Load a “better than nothing” default font. Well, it is very clear. But can I provide some "better than nothing" default for my own use?

What are your OS, Python and Pillow versions?

  • OS: Ubuntu
  • Python: 3.8.13
  • Pillow: 9.2.0
from PIL import Image, ImageDraw

im = Image.new("RGB", (400, 300))
draw = ImageDraw.Draw(im)
draw.text((10, 10), "你好")

im.show()

It gives:

AttributeError: 'ImageFont' object has no attribute 'getmask2'

guoquan avatar Aug 05 '22 13:08 guoquan

If you're looking for an immediate solution, you could patch Pillow. Using one of our test fonts,

from PIL import ImageFont
ImageFont.load_default = lambda: ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf")

In context,

from PIL import Image, ImageDraw, ImageFont
ImageFont.load_default = lambda: ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf")

im = Image.new("RGB", (400, 300))
draw = ImageDraw.Draw(im)
draw.text((10, 10), "你好")

im.show()

radarhere avatar Aug 05 '22 13:08 radarhere

Awesome! I am practicing an identical workaround. However, patching load_default to set default isn't something we want to write in the documentation. Looking forward to a consistent and reliable interface to handle this.

guoquan avatar Aug 05 '22 15:08 guoquan

What do you think of this interface for setting a default font?

from PIL import ImageDraw, ImageFont
ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)

I've created PR #6484 with this change.

radarhere avatar Aug 06 '22 07:08 radarhere

Looks good, but why does it go under ImageDraw rather than ImageFont? Is ImageFont.load_default used somewhere else where we want flexibility over consistency?

guoquan avatar Aug 08 '22 09:08 guoquan

You can already set the default font on a per-ImageDraw instance basis, e.g.

from PIL import Image, ImageDraw, ImageFont

im = Image.new("RGB", (400, 300))
draw = ImageDraw.Draw(im)
draw.font = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf")
draw.text((10, 10), "你好")

im.show()

This is currently undocumented, but I wouldn't expect it to be removed without a deprecation notice. Perhaps just documenting this field would be good enough for you?

@radarhere's PR adds the option to set a default value globally for all ImageDraw instances without an explicit font set.

ImageFont.load_default isn't used anywhere else (except for a small number of tests), but despite being a function, it can be treated sort of like a getter for a constant. The function contains a base64 encoded font file that it simply loads and returns every time you call it. I would therefore find it strange to change its behaviour by adding a set_default function.

nulano avatar Aug 08 '22 22:08 nulano

Yeah, the constant constraint makes sense to me.

guoquan avatar Aug 09 '22 02:08 guoquan