Сейчас я напишу тебе программу на языке python, на входе которой словарь с данными, а на выходе рисуется картинка, содержащая таблицу.
Программа:
import pytz
import datetime
import time
import requests

from babel.dates import format_date, format_datetime, format_time, get_timezone, UTC

from PIL import Image, ImageDraw, ImageFont


def draw_date_header(draw, text, font, row_height, start_coordinate_x, start_coordinate_y):
    cell_text = text
    
    # Находим размер текста с помощью метода font.getbbox
    text_bbox = font.getbbox(cell_text)
    text_height = text_bbox[3]
    
    text_x = start_coordinate_x
    text_y = start_coordinate_y + (row_height - text_height) // 2

    # Рисуем тень
    shadow_color = (0, 0, 0)
    shadow_x = text_x + 2
    shadow_y = text_y + 2
    draw.text((shadow_x, shadow_y), text, fill=shadow_color, font=font)

    draw.text((text_x, text_y), cell_text, fill=(255, 255, 255), font=font)
    return draw


def find_table_width(column_widths, column_spacing):
    cell_width = 0
    for i in range(0, len(column_widths)):    
        cell_width = cell_width + column_widths[i] + column_spacing
    return cell_width


def add_weather_icon(image, weather_icon, cell_width, row_height, start_coordinate_x, start_coordinate_y):
    cell_image = Image.open(weather_icon)
    cell_image.thumbnail((40, 40))
    cell_image_width, cell_image_height = cell_image.size
    image_x = start_coordinate_x + (cell_width - cell_image_width) // 2
    image_y = start_coordinate_y + (row_height - cell_image_height) // 2
    image.paste(cell_image, (image_x, image_y), cell_image)


def get_grandprix_schedule():
    y = requests.get("http://ergast.com/api/f1/current.json").json()
    grandprixs = y["MRData"]["RaceTable"]["Races"]
    return grandprixs


def eu_time_to_moscow(input_date):
    date_time_obj = datetime.datetime.strptime(input_date, "%Y-%m-%d %H:%M:%S")
    moscow_time = format_datetime(date_time_obj, "Y-MM-d H:mm:ss", tzinfo=get_timezone(pytz.timezone('Etc/GMT-3')), locale='ru_RU')
    return moscow_time

  
def format_date(input_date):
    # Преобразование строки в объект datetime
    date_time_obj = datetime.datetime.strptime(input_date, "%Y-%m-%d %H:%M:%S")
    formatted_date = format_datetime(date_time_obj, "EEEE, d MMMM", tzinfo=get_timezone(pytz.timezone('Etc/GMT-3')), locale='ru_RU').capitalize()
    formatted_time = format_datetime(date_time_obj, "HH:mm", tzinfo=get_timezone(pytz.timezone('Etc/GMT-3')), locale='ru_RU')
  
    return formatted_date, formatted_time


def draw_text(draw, font, text, cell_width, row_height, start_coordinate_x, start_coordinate_y):
    # Находим размер текста с помощью метода font.getbbox
    text_bbox = font.getbbox(text)
    text_width = text_bbox[2] - text_bbox[0]
    text_height = text_bbox[3]
    
    text_x = start_coordinate_x + (cell_width - text_width) // 2
    text_y = start_coordinate_y + (row_height - text_height) // 2

    # Делим текст на строки
    lines = text.split("\n")
    line_count = len(lines)

    # Вычисляем высоту строки для каждой строки текста
    line_height = text_height // line_count

    # Рассчитываем стартовую точку для первой строки
    first_line_y = text_y - (line_count // 2) * line_height

    # Рисуем каждую строку текста
    for i, line in enumerate(lines):
        current_line_y = first_line_y + i * line_height
        draw.text((text_x, current_line_y), line, fill=(255, 255, 255), font=font)
    
    # Рисуем тень
    #shadow_color = (0, 0, 0)
    #shadow_x = text_x + 2
    #shadow_y = text_y + 2
    #draw.text((shadow_x, shadow_y), text, fill=shadow_color, font=font)
  
    #draw.text((text_x, text_y), text, fill=(255, 255, 255), font=font)


def draw_run_information(image, draw, font, run, column_widths, column_spacing, row_height, start_coordinate_x, start_coordinate_y):
    TINT_COLOR = (0, 0, 0)  # Black
    TRANSPARENCY = 0.3  # Degree of transparency, 0-100%
    OPACITY = int(255 * TRANSPARENCY)
    overlay = Image.new('RGBA', image.size, TINT_COLOR+(0,))

    draw_overlay = ImageDraw.Draw(overlay)  # Create a context for drawing things on it.

    original_image = image

    for col in range(5):
        cell_width = column_widths[col]

        # Рисуем ячейку
        draw_overlay.rectangle([(start_coordinate_x, start_coordinate_y), (start_coordinate_x + cell_width, start_coordinate_y + row_height)], outline=(255, 165, 0, 72), fill=TINT_COLOR+(OPACITY,))
        
        if col == 0:  # Для первого столбца
            add_weather_icon(overlay, run["run_weathercode"], cell_width, row_height, start_coordinate_x, start_coordinate_y)
        
        elif col == 1:
            draw_text(draw_overlay, font, run["run_name"], cell_width, row_height, start_coordinate_x, start_coordinate_y)
        
        elif col == 2:
            draw_text(draw_overlay, font, run["run_temperature"], cell_width, row_height, start_coordinate_x, start_coordinate_y)

        elif col == 3:
            draw_text(draw_overlay, font, run["run_rain_probability"], cell_width, row_height, start_coordinate_x, start_coordinate_y)
          
        elif col == 4:
            draw_text(draw_overlay, font, run["run_time"], cell_width, row_height, start_coordinate_x, start_coordinate_y)

        start_coordinate_x += cell_width + column_spacing  # переходим к следующей ячейке
      
    # Alpha composite these two images together to obtain the desired result.
    original_image = Image.alpha_composite(original_image, overlay)  
    image = original_image
    return image


def draw_table_with_dates(runs):
    # Открываем изображение фона
    background = Image.open("background.png")
    image_width, image_height = background.size
  
    # Создаем изображение размером 1080x1350 пикселей
    image = Image.new("RGBA", (1080, 1350), (255, 255, 255))
    image.paste(background, (0, 0))
    draw = ImageDraw.Draw(image)

    # Задаем параметры таблицы
    row_height = 75
    column_widths = [75, 305, 125, 125, 150]
    column_spacing = 10
    row_spacing = 10

    run_data_font = ImageFont.truetype("RunDataFont.ttf", size=25)
    date_header_font = ImageFont.truetype("DateHeaderFont.ttf", size=35)
    
    # Вычисляем положение таблицы
    table_width = sum(column_widths) + column_spacing * (len(column_widths) - 1)
    table_height = row_height * 5

    x = 135
    y = 500

    current_table_row = 0
    current_run = 0
    active_run_flag = 0
    # Рисуем таблицу
    for row in range(8):
        if current_table_row == 0:
            draw = draw_date_header(draw, runs[current_run]["run_date"], date_header_font, row_height, x, y)

        elif current_table_row == 1:
            image = draw_run_information(image, draw, run_data_font, runs[current_run], column_widths, column_spacing, row_height, x, y)
            current_run += 1
      
        elif current_table_row >= 2:
            if runs[current_run]["run_date"] != runs[current_run - 1]["run_date"] and active_run_flag == 0:
                draw = draw_date_header(draw, runs[current_run]['run_date'], date_header_font, row_height, x, y)
                active_run_flag = 1

            else:
                image = draw_run_information(image, draw, run_data_font, runs[current_run], column_widths, column_spacing, row_height, x, y)
                current_run += 1
                active_run_flag = 0

        # Переходим на новую строку
        x = (image.width - table_width) // 2
        y += row_height + row_spacing
        current_table_row += 1

    # Сохраняем изображение в файл
    image.save("table_with_dates.png")


# Пример использования функции с примером словаря данных
runs = [
  {"run_name": "Практика 1", "run_date": "Пятница, 21 июля", "run_time": "13:00", "run_temperature": "25", "run_rain_probability": "30", "run_weathercode": "weather_icons/1000.png"},
  {"run_name": "Квалификация\nк гонке", "run_date": "Пятница, 21 июля", "run_time": "16:00", "run_temperature": "26", "run_rain_probability": "35", "run_weathercode": "weather_icons/1001.png"},
  {"run_name": "Квалификация\nк спринту", "run_date": "Суббота, 22 июля", "run_time": "13:00", "run_temperature": "27", "run_rain_probability": "40", "run_weathercode": "weather_icons/1100.png"},
  {"run_name": "Спринт", "run_date": "Суббота, 22 июля", "run_time": "17:00", "run_temperature": "28", "run_rain_probability": "50", "run_weathercode": "weather_icons/1101.png"},
  {"run_name": "Гонка", "run_date": "Воскресенье, 23 июля", "run_time": "18:00", "run_temperature": "29", "run_rain_probability": "60", "run_weathercode": "weather_icons/1102.png"}
]

#yaya = get_grandprix_schedule()
asd = "2023-07-15 14:32:00"
zxc = eu_time_to_moscow(asd)
draw_table_with_dates(runs)

Сделай так, чтобы в функции draw_text текст, который написан в две линии, выравнивался по ширине относительно содержащихся в нем слов.