pythonpython-imaging-library

how to get the size of a text with line breaks with Pillow Python


I am trying to center a text inside a box using Pillow. I have followed the instructions in this stackoverflow post, which gives the desired result. However, if I add an line break between the words, the text does not get resized properly and it gets offcentered.

Here an example of the code without the line break:

title_text = "Hello World"

img = Image.new(size=(400, 300), mode='RGB')
draw = ImageDraw.Draw(img)
font_path = "/content/Charlie don't surf.ttf"

# draw white rectangle 200x100 with center in 200,150
draw.rectangle((200-100, 150-50, 200+100, 150+50), fill='white')
draw.line(((0, 150), (400, 150)), 'gray')
draw.line(((200, 0), (200, 300)), 'gray')

# find font size for text `"Hello World"` to fit in rectangle 200x100
selected_size = 1
for size in range(1, 150):
    title_font = ImageFont.FreeTypeFont(font_path, size=size)
    w, h = title_font.getsize(title_text)
    
    if w > 200 or h > 100:
        break
        
    selected_size = size
    
# draw text in center of rectangle 200x100        
title_font = ImageFont.FreeTypeFont(font_path, size=selected_size)

draw.text((200-(w//2), 150-h//2), title_text, fill='red', font=title_font)

display(img)

Note that the text is centered and resized as desired

Note that the text is centered and resized as desired.

But if I change title_text = "Hello World" to title_text = "Hello\nWorld" to add an line break. I get this:

title_text = "Hello\nWorld"

img = Image.new(size=(400, 300), mode='RGB')
draw = ImageDraw.Draw(img)
font_path = "/content/Charlie don't surf.ttf"

# draw white rectangle 200x100 with center in 200,150
draw.rectangle((200-100, 150-50, 200+100, 150+50), fill='white')
draw.line(((0, 150), (400, 150)), 'gray')
draw.line(((200, 0), (200, 300)), 'gray')

# find font size for text `"Hello World"` to fit in rectangle 200x100
selected_size = 1
for size in range(1, 150):
    title_font = ImageFont.FreeTypeFont(font_path, size=size)
    w, h = title_font.getsize(title_text)
    
    if w > 200 or h > 100:
        break
        
    selected_size = size
    
# draw text in center of rectangle 200x100        
title_font = ImageFont.FreeTypeFont(font_path, size=selected_size)

draw.text((200-(w//2), 150-h//2), title_text, fill='red', font=title_font)

display(img)

enter image description here

Note that with the line break, the text is not properly resized and is also not centered. Apparently the .getsize() method does not handle line breaks well.

I am using Pillow version 7.1.2 and running all this in a Google Colab Notebook (that is the reason why I am running display(img)).

What can I do to fix this issue? My end goal is to use Python to create cards for a game. Some cards will have two words and other cards will have only one word. I would like my code to be able to resize and center the text regardless the number of words.

Edit: After some test, I have noticed that .getsize() treats the string "Hello\nWorld" as a single line of text and not as two lines. My question could be: how to get the size of a text with line breaks?


Solution

  • I have found the solution. The final code looks like this:

    title_text = "Hello\nWorld"
    
    img = Image.new(size=(400, 300), mode='RGB')
    draw = ImageDraw.Draw(img)
    font_path = "/content/Charlie don't surf.ttf"
    
    # draw white rectangle 200x100 with center in 200,150
    draw.rectangle((200-100, 150-50, 200+100, 150+50), fill='white')
    draw.line(((0, 150), (400, 150)), 'gray')
    draw.line(((200, 0), (200, 300)), 'gray')
    
    # find font size for text `"Hello World"` to fit in rectangle 200x100
    selected_size = 1
    for size in range(1, 150):
        title_font = ImageFont.FreeTypeFont(font_path, size=size)
        #w, h = title_font.getsize(title_text)
        w, h = draw.textsize(title_text, title_font)
        
        if w > 200 or h > 100:
            break
            
        selected_size = size
        
    # draw text in center of rectangle 200x100        
    title_font = ImageFont.FreeTypeFont(font_path, size=selected_size)
    
    draw.text((200-(w//2)+5, 150-h//2), title_text, fill='red', font=title_font, align = "center")
    
    display(img)
    

    The final result is:

    enter image description here

    I have used resources I found on this page. More specifically I found out that you can use draw.textsize(title_text, title_font) to get a more precise size of the text, especially when it has line breaks.