我又尝试了一次,我做到了!
代码如下:
def fit_text(text, text_size, text_color, max_horizontal_chars, box_outline_color, box_background_color,
box_outline_width, font_file, word_bank_outline, high_res):
"""
:param text: The text that needs to be fir inside the bounding box (words separated by ,)
:type text: str
:param text_size: The size of the text
:type text_size: int
:param text_color: The color of the text
:type text_color: Tuple(int, int, int) | str
:param max_horizontal_chars: The number of the max horizontal chars allowed in one line
:type max_horizontal_chars: int
:param box_outline_color: The color of the outline of the bounding box
:type box_outline_color: Tuple(int, int, int) | str
:param box_background_color: The color of the background of the bounding box
:type box_background_color: Tuple(int, int, int) | str
:param box_outline_width: The thickness of the outline of the bounding box
:type box_outline_width: int
:param font_file: The file of the font of the text
:type font_file: str
:param word_bank_outline: Whether there will be an outline or not
:type word_bank_outline: bool
:return: The image
:rtype: Image
"""
multiplier = 2
text_size *= multiplier //2
# Replaces every ',' to '-' because the wrapper library will associate '-' as a separator and sorts by length
text = text.split(',')
text.sort(key=len, reverse=True)
text = ','.join(text).upper()
text = text.replace(',', '-')
# Changes the text size to fit the box if the longest word cannot fit in one line
font = ImageFont.truetype(font_file, text_size)
longest_word = sorted(text.split('-'), key=len)[-1]
if longest_word[0] == ' ':
longest_word = longest_word[1:]
longest_word_size = font.getsize(longest_word)[0]
while longest_word_size >= 1100:
longest_word = sorted(text.split('-'), key=len)[-1]
if longest_word[0] == ' ':
longest_word = longest_word[1:]
longest_word_size = font.getsize(longest_word)[0]
text_size -= 1
font = ImageFont.truetype(font_file, text_size)
# Initializes the text wrapper
wrapper = textwrap.TextWrapper()
wrapper.max_lines = 3
wrapper.placeholder = '...'
wrapper.break_long_words = False
wrapper.width = max_horizontal_chars
# Wrap the text
text = wrapper.fill(text=text)
# Create a new image according to the size of the text
img = Image.new('RGBA', (font.getsize_multiline(text)[0], (font.getsize_multiline(text)[1]+20+(60 if high_res else 20)+box_outline_width+(text.count('\n')*10))), (0,0,0,0))
# Initializes the ImageDraw.Draw for the img so I can draw on it
draw = ImageDraw.Draw(im=img)
# If word_bank_outline is true it will draw a bunch or rectangles according to box_outline_width
if word_bank_outline:
w, h = img.size
for i in range(0, box_outline_width):
shape = [(0 + i, 0 + i), (w - i, h - i)]
draw.rectangle(shape, box_background_color, box_outline_color)
# Replaces every '-' back to ','
text = text.replace('-', ',').upper()
# Checks if the first char is ' ' if it is it will be cut out
if text[0] == ' ':
text = text[1:]
# Draws the text onto the bounding box
draw.multiline_text(xy=(10 + (20 if high_res else 0), 0), text=text, font=font, fill=text_color, spacing=20)
return img
基本上实现它的主要因素是函数ttffont.getsize,它以像素为单位返回字符串的大小,因此我可以知道字符串的确切大小,我可以更准确地适应它,另一件事是库textwraper 将文本分成单独的行。
所以首先它会检查字符串中最长的单词是否可以容纳在边界框内,如果不是,它将循环并减小文本大小直到它适合
然后它会调用 textwrapper.fill 来分割文本,然后它只会在图像上绘制文本并绘制轮廓,然后它会返回图像。
最终结果: