Home > Enterprise >  Writing different text fonts on the same line using PIL
Writing different text fonts on the same line using PIL

Time:03-18

I'm trying to write prices on an Image using two different fonts on the same line, one for the numbers, and one for the currency symbol (in this case, €), so I am wondering what is the proper way to do it using PIL. What I'm currently trying is to use font.getsize() on the first font, and add the X axis result of the getsize to the coordinates of the text in order to get the offset needed to write the symbol:

d_price = "248"

d_price_font = ImageFont.truetype("Gotham Black Regular.ttf", 58)
symbol_font = ImageFont.truetype("Myriad Pro Regular.ttf", 70)

d_price_size = d_price_font.getsize(d_price)
d_price_coords = (677, 519)

draw.text(d_price_coords, d_price, (0, 255, 255), font=d_price_font)
symbol_coords = (d_price_coords[0]   d_price_size[0], d_price_coords[1])
draw.text(symbol_coords, "€", (255, 255, 255), font=symbol_font)

This seem to work, however this would require me to calculate the size of the text everytime I want to write a symbol next to a price, and also adjust the font size, which is different to the one I'm using to the prices.

enter image description here

CodePudding user response:

I think your approach is OK, but there's a nicer way to write the code that will avoid the repetition and make it easier to add more text in varied font:

from PIL import ImageFont, Image, ImageDraw


def draw_text_in_font(d: ImageDraw, coords: tuple[int, int], 
                      content: list[tuple[str, tuple[int, int, int], str, int]]):
    fonts = {}
    for text, color, font_name, font_size in content:
        font = fonts.setdefault(font_name, ImageFont.truetype(font_name, font_size))
        d.text(coords, text, color, font)
        coords = (coords[0]   font.getsize(text)[0], coords[1])


with Image.open("white.png") as im:
    draw = ImageDraw.Draw(im)

    draw_text_in_font(draw, (10, 10), [
        ('248', (255, 255, 0), 'C:/Windows/Fonts/Consola.ttf', 70),
        ('€', (255, 0, 255), 'C:/Windows/Fonts/Arial.ttf', 56)
    ])

    im.show()

This just bundles all you were doing in a single, fairly readable function, while calling it is pretty clear as well. You can string on more text in other fonts, and it will reuse a font once it's created it.

It might be nicer to use a list[dict[str, Any]] for the content, or even define a dataclass for it, so you could make it clearer in your code what the values are that you're passing to draw_text_in_font, but the idea would be the same.

Also, if you wrote a class instead, you could cache the fonts you use in class attributes, to avoid creating them every time you write a text, but that's all just niceties.

Note that I like making the types explicit, as it helps whoever is using the code to provide the right arguments in a decent editor or IDE. But this could would work the same, just not as nice to the coder:

def draw_text_in_font(d, coords, content):
    fonts = {}
    for text, color, font_name, font_size in content:
        font = fonts.setdefault(font_name, ImageFont.truetype(font_name, font_size))
        d.text(coords, text, color, font)
        coords = (coords[0]   font.getsize(text)[0], coords[1])
  • Related