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

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 add_termometer_icon(image, font, rain_probability, cell_width, row_height, start_coordinate_x, start_coordinate_y):
    cell_image = Image.open("general_icons/termometer.png")
    cell_image.thumbnail((35, 35))
    cell_image_width, cell_image_height = cell_image.size
    text_bbox = font.getbbox(rain_probability)
    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
    draw.text((text_x, text_y), text, fill=(255, 255, 255), font=font)
  
    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):
    # Делим текст на строки
    lines = textwrap.wrap(text, width=max(12, 1))
    if len(lines) == 1:
        #Находим размер текста с помощью метода 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
        draw.text((text_x, text_y), text, fill=(255, 255, 255), font=font)
    else:
        max_width = 0
        general_height = 0
        line_spacing = 5
        for word in lines:
          word_bbox = font.getbbox(word)
          word_width = word_bbox[2] - word_bbox[0]
          word_height = word_bbox[3]
          general_height += word_height
          if word_width > max_width:
            max_width = word_width
            text_x = start_coordinate_x + (cell_width - max_width) // 2
        general_height = general_height + (line_spacing * (len(lines) - 1))
        text_y = start_coordinate_y + (row_height - general_height) // 2
        draw.text((text_x, text_y), text, fill=(255, 255, 255), font=font, spacing=line_spacing, align="center")
        
    # Вычисляем высоту строки для каждой строки текста
    #line_height = text_height // len(lines)

    # Рассчитываем стартовую точку для первой строки
    #first_line_y = text_y - (len(lines) // 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)


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": "Квалификация к гонке", "run_date": "Пятница, 21 июля", "run_time": "16:00", "run_temperature": "26", "run_rain_probability": "35", "run_weathercode": "weather_icons/1001.png"},
  {"run_name": '''Квалификация
  к спринту''', "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"}
]
Сделай так, чтобы в каждую третью ячейку таблицы, вставлялась картинка termometer.png и текст "25", которые должны быть выровнены по высоте и ширине ячейки.