improve font size calculation algorithm

This commit is contained in:
LBlend committed 2023-05-09 01:50:01 +09:00
1 parent f9f4c3e75a
commit 728302b8fc
1 file changed
+53 -43
+53 -43
View File
@@ -70,39 +70,7 @@ class Meme(commands.Cog):
await interaction.followup.send(file=discord.File(output, "deepfry.jpg"))
@staticmethod
def __format_text(text: str, textbox_width: int) -> tuple[str, ImageFont.FreeTypeFont]:
"""
Formats text to fit in a textbox of a given width.
Parameters
----------
text (str): Text to be formatted.
textbox_width (int): Width of the textbox.
Returns
----------
tuple[str, ImageFont.FreeTypeFont]: The formatted text and the font, with its size set, to be used.
"""
if len(text) <= 1:
font_size = 150
else:
font_size = textbox_width // (len(text) // 6)
if font_size < 20:
font_size = 20
elif font_size > 150:
font_size = 150
font = ImageFont.truetype("./src/assets/fonts/RobotoMono-Medium.ttf", font_size)
line_length = int(textbox_width // font_size * 1.5)
text = textwrap.fill(text, width=line_length)
return text, font
@staticmethod
def __draw_text(image: Image.Image, text: str, font: ImageFont.FreeTypeFont, offset: tuple = (0, 0)):
def __draw_text(image: Image.Image, text: str, offset: tuple = (0, 0)):
"""
Draws meme text on preferred meme template.
@@ -115,13 +83,59 @@ class Meme(commands.Cog):
"""
box_size = (540, 540)
box = Image.new("RGB", box_size, (255, 255, 255))
draw = ImageDraw.Draw(box)
text_box = draw.multiline_textbbox(
(box_size[0], box_size[1]), text=text, font=font, align="center", anchor="mm"
)
font_path = "./src/assets/fonts/RobotoMono-Medium.ttf"
# Calculate initial font size
# This is done by increasing the font size until the text is too wide
max_width, max_height = box_size[0] * 0.9, box_size[1] * 0.9 # 90% of the box size
font_size = 20 # Initiale size. I consider this the smallest readable size
font_width = 0 # Initiale width. This is just to make sure the while loop runs at least once
while font_width < max_width:
font = ImageFont.truetype(font_path, font_size)
font_box = font.getbbox(text)
font_width = font_box[2] - font_box[0]
font_size += 2
# Wrap the text based on our current font size
text = textwrap.fill(text, width=font_size)
# We then have to calculate the font size again
#
# The problem is, font.getbbox() doesn't work with multiline text so it returns the size without the linebreaks
# We can get around this by calculating the size of the longest line in the text
# and then increasing the font size until that line is too wide
#
# We also have to make sure it doesn't get too tall
longest_line = max(text.split("\n"), key=len)
longest_line_font_box = font.getbbox(longest_line)
font_width = longest_line_font_box[2] - longest_line_font_box[0]
# Estimate font height
# This is done by multiplying the height of the original non-wrapped text with the number of lines
# and then adding 20 to account for the line spacing.
# This of course is dependant on font size itself, but it's good enough for now
font_height = (font_box[3] - font_box[1] + 20) * text.count("\n")
max_height = box_size[1] * 0.9
while font_width < max_width and font_height < max_height:
font = ImageFont.truetype(font_path, font_size)
longest_line_font_box = font.getbbox(longest_line)
font_width = longest_line_font_box[2] - longest_line_font_box[0]
font_box = font.getbbox(text)
font_height = (font_box[3] - font_box[1] + 20) * text.count("\n")
font_size += 2
# Draw textbox and text
text_box = draw.multiline_textbbox(box_size, text=text, font=font, align="center", anchor="mm")
text_box = box.size[0] // 2, box.size[1] // 2
draw.multiline_text(text_box, text, font=font, fill=(0, 0, 0, 255), align="center", anchor="mm")
@@ -146,13 +160,9 @@ class Meme(commands.Cog):
# Fetch meme template
template = Image.open("./src/assets/misc/sivert_goodbad.jpg")
# Calculate font sizes
top_text, top_font = Meme.__format_text(dårlig_tekst, 540)
bottom_text, bottom_font = Meme.__format_text(bra_tekst, 540)
# Draw text
Meme.__draw_text(template, top_text, top_font, offset=(540, 0))
Meme.__draw_text(template, bottom_text, bottom_font, offset=(540, 540))
Meme.__draw_text(template, dårlig_tekst, offset=(540, 0))
Meme.__draw_text(template, bra_tekst, offset=(540, 540))
# Save image to buffer
output = BytesIO()