CardGenerator / render_card_text.py
drakosfire's picture
more cleanup, removed torch, not needed in api scenario
60c7d21
from PIL import ImageDraw, ImageFont
# Function for managing longer bodies of text and breaking into a list of lines to be printed based on input arguments
def split_text_into_lines(text, font, max_width, draw):
blocks = text.split('\n')
lines = []
for block in blocks:
words = block.split()
current_line = ''
for word in words:
# Check width with new word added
test_line = f"{current_line} {word}".strip()
test_width = draw.textlength(text = test_line, font=font)
if test_width <= max_width:
current_line = test_line
else:
#If the line with the new word exceeds the max width, start a new line
lines.append(current_line)
current_line = word
# add the last line
lines.append(current_line)
return lines
# Function for calculating the height of the text at the current font setting
def adjust_font_size_lines_and_spacing(text, font_path, initial_font_size, max_width, area_height, image) :
font_size = initial_font_size
optimal_font_size = font_size
optimal_lines = []
line_spacing_factor = 1.2 # multiple of font size that will get added between each line
while font_size > 10: # Set minimum font size
font = ImageFont.truetype(font_path, font_size)
draw = ImageDraw.Draw(image)
# Fitting text into box dimensions
lines = split_text_into_lines(text, font, max_width, draw)
# Calculate total height with dynamic line spacing
single_line_height = draw.textbbox((0, 0), "Ay", font=font)[3] - draw.textbbox((0, 0), "Ay", font=font)[1] # Height of 'Ay'
line_spacing = int(single_line_height * line_spacing_factor) - single_line_height
total_text_height = len(lines) * single_line_height + (len(lines) - 1) * line_spacing # Estimate total height of all lines by multiplying number of lines by font height plus number of lines -1 times line spacing
if total_text_height <= area_height :
optimal_font_size = font_size
optimal_lines = lines
break # Exit loop font fits in contraints
else:
font_size -= 1 # Reduce font by 1 to check if it fits
return optimal_font_size, optimal_lines, line_spacing
# Function that takes in an image,text and properties for textfrom card_generator
def render_text_with_dynamic_spacing(image, text, center_position, max_width, area_height,font_path, initial_font_size,description = None, quote = None):
optimal_font_size, optimal_lines, line_spacing = adjust_font_size_lines_and_spacing(
text, font_path, initial_font_size, max_width, area_height, image)
# create an object to draw on
font = ImageFont.truetype(font_path, optimal_font_size)
draw = ImageDraw.Draw(image)
# Shadow settings
shadow_offset = (1, 1) # X and Y offset for shadow
shadow_color = 'grey' # Shadow color
# Unsure about the following line, not sure if I want y_offset to be dynamic
y_offset = center_position[1]
if description or quote :
for line in optimal_lines:
line_width = draw.textlength(text = line, font=font)
x = center_position[0]
# Draw Shadow first
shadow_position = (x + shadow_offset[0], y_offset + shadow_offset[1])
draw.text(shadow_position, line, font=font, fill=shadow_color)
#Draw text
draw.text((x, y_offset), line, font=font, fill = 'black', align = "left" )
y_offset += optimal_font_size + line_spacing # Move to next line
return image
for line in optimal_lines:
line_width = draw.textlength(text = line, font=font)
x = center_position[0] - (line_width / 2)
# Draw Shadow first
shadow_position = (x + shadow_offset[0], y_offset + shadow_offset[1])
draw.text(shadow_position, line, font=font, fill=shadow_color)
#Draw text
draw.text((x, y_offset), line, font=font, fill = 'black', align = "left" )
y_offset += optimal_font_size + line_spacing # Move to next line
return image
# Function to put the description objects together, this will be the complicated bit, I think iterate through keys excluding title, type and cost