Центрирование русского текста на картинке

Я пишу модуль для бота который генерирует демотиватор по заданному ему тексту и изображению. Бот ориентируется на русскую аудиторию, поэтому использование русского должно присутствовать.

Что вышло:

PADDING = 25
LINE_SIZE = 2
BOTTOM_PADDING = 150
FONTS = f"{os.getcwd()}/fonts"

def set_text(template, texts):
        font_upper = ImageFont.truetype(f"{FONTS}/times.ttf", 64)
        font_lower = ImageFont.truetype(f"{FONTS}/times.ttf", 46)

        draw_upper_text = ImageDraw.Draw(template)
        upper_text_width, _ = draw_upper_text.textsize(texts[0].encode("utf-8").decode("latin1"), font_upper)
        draw_upper_text.text(
            (
                (template.width-upper_text_width)/2,
                template.height-BOTTOM_PADDING+10
            ),
            texts[0], fill="white", font=font_upper
        )

        draw_lower_text = ImageDraw.Draw(template)
        lower_text_width, _ = draw_lower_text.textsize(texts[1].encode("utf-8").decode("latin1"), font_lower)
        draw_lower_text.text(
            (
                (template.width-lower_text_width)/2,
                template.height-BOTTOM_PADDING+(PADDING*4)-10
            ),
            texts[1], fill="white", font=font_lower
        )
        return template

пример того, что получается

Текст с русскими символами "съезжает", а с латинскими - всё хорошо. Как можно пофиксить?


Ответы (1 шт):

Автор решения: gil9red

Уберите .encode("utf-8").decode("latin1"), это лишнее действие, которое для кириллицы увеличивает размер текста в 2 раза

Причина в том, что текст там в utf-8, а в нем кириллица занимает 2 байта на символ. А когда вы сделали encode вы получили массив байт, а .decode("latin1") из массива байт вернул строку, причем в latin1 символы занимают 1 байт, поэтому:

  • Строка с кириллицей стала в 2 раза больше
  • Она стала кракозяброй
  • Строка с латинскими буквами не поменялась, т.к. символы в ней занимают 1 байт и их коды совпадают с тем, что в utf-8

Как это выглядит:

text = texts[0].encode("utf-8").decode("latin1")
print(repr(texts[0]), len(texts[0]), len(text), repr(text))
# 'ПРИВЕТ' 6 12 'Ð\x9fÐ\xa0Ð\x98Ð\x92Ð\x95Т'

text = texts[1].encode("utf-8").decode("latin1")
print(repr(texts[1]), len(texts[0]), len(text), repr(text))
# 'HELLO WORLD!' 6 12 'HELLO WORLD!'

Пример:

from PIL import ImageFont, Image, ImageDraw


PADDING = 25
LINE_SIZE = 2
BOTTOM_PADDING = 150
FONTS = "fonts"


def set_text(template, texts):
    font_upper = ImageFont.truetype(f"{FONTS}/times.ttf", 64, encoding='utf-8')
    font_lower = ImageFont.truetype(f"{FONTS}/times.ttf", 46, encoding='utf-8')

    draw_upper_text = ImageDraw.Draw(template)
    upper_text_width, _ = draw_upper_text.textsize(texts[0], font_upper)
    draw_upper_text.text(
        (
            (template.width-upper_text_width)/2,
            template.height-BOTTOM_PADDING+10
        ),
        texts[0], fill="white", font=font_upper
    )

    draw_lower_text = ImageDraw.Draw(template)
    lower_text_width, _ = draw_lower_text.textsize(texts[1], font_lower)
    draw_lower_text.text(
        (
            (template.width-lower_text_width)/2,
            template.height-BOTTOM_PADDING+(PADDING*4)-10
        ),
        texts[1], fill="white", font=font_lower
    )
    return template


img = Image.new("RGB", (400, 300), "black")

set_text(img, ["ПРИВЕТ", "HELLO WORLD!"])

img.show()

Результат:

введите сюда описание изображения

→ Ссылка