def render_text(text, *, size=(800, 600), bg=None, fg=(255, 255, 255), font_name=None, lines=2) -> Image.Image:
import pygame
import textwrap
pygame.font.init() # Only init what we need
width, height = size
MARGIN = 40
# Natural wrapping
wrapped = textwrap.wrap(text, width=30)[:int(lines)]
img = pygame.Surface(size)
if isinstance(bg, tuple):
img.fill(bg)
elif isinstance(bg, Image.Image):
bg = bg.resize(size)
bg_data = pygame.image.fromstring(bg.tobytes(), size, bg.mode)
img.blit(bg_data, (0, 0))
else:
img.fill((0, 0, 0))
font_size = 200
while font_size > 10:
font = pygame.font.SysFont(font_name, font_size)
rendered = [font.render(line, True, fg) for line in wrapped]
max_width = max((r.get_width() for r in rendered), default=0)
total_height = sum(r.get_height() for r in rendered) + MARGIN * (len(rendered) - 1)
if max_width <= width - 2 * MARGIN and total_height <= height - 2 * MARGIN:
break
font_size -= 2
# Center vertically
y = (height - total_height) // 2
for r in rendered:
x = (width - r.get_width()) // 2
img.blit(r, (x, y))
y += r.get_height() + MARGIN
pygame.font.quit() # Clean up
return Image.frombytes("RGB", size, pygame.image.tostring(img, "RGB"))