python PIL dibujar texto multilínea en la imagen

Intento agregar texto en la parte inferior de la imagen y en realidad lo he hecho, pero en el caso de que mi texto sea más largo que el ancho de la imagen, se corta desde ambos lados, para simplificar, me gustaría que el texto esté en varias líneas, si es más largo que el ancho de la imagen. Aquí está mi código:

FOREGROUND = (255, 255, 255) WIDTH = 375 HEIGHT = 50 TEXT = 'Chyba najwyższy czas zadać to pytanie na śniadanie \n Chyba najwyższy czas zadać to pytanie na śniadanie' font_path = '/Library/Fonts/Arial.ttf' font = ImageFont.truetype(font_path, 14, encoding='unic') text = TEXT.decode('utf-8') (width, height) = font.getsize(text) x = Image.open('media/converty/image.png') y = ImageOps.expand(x,border=2,fill='white') y = ImageOps.expand(y,border=30,fill='black') w, h = y.size bg = Image.new('RGBA', (w, 1000), "#000000") W, H = bg.size xo, yo = (Ww)/2, (Hh)/2 bg.paste(y, (xo, 0, xo+w, h)) draw = ImageDraw.Draw(bg) draw.text(((w - width)/2, w), text, font=font, fill=FOREGROUND) bg.show() bg.save('media/converty/test.png') 

Podría usar textwrap.wrap para dividir el text en una lista de cadenas, cada una con un width máximo de caracteres:

 import textwrap lines = textwrap.wrap(text, width=40) y_text = h for line in lines: width, height = font.getsize(line) draw.text(((w - width) / 2, y_text), line, font=font, fill=FOREGROUND) y_text += height 

La respuesta aceptada envuelve el texto sin medir la fuente (máx. 40 caracteres, sin importar el tamaño de la fuente y el ancho del cuadro), por lo que los resultados son solo aproximados y pueden sobrellenar o llenar el cuadro fácilmente.

Aquí hay una biblioteca simple que resuelve el problema correctamente: https://gist.github.com/turicas/1455973

Todas las recomendaciones sobre el uso de textwrap no logran determinar el ancho correcto para fonts no monoespaciadas (como Arial, utilizadas en el código de ejemplo de tema).

He escrito una clase de ayuda simple para envolver el texto con respecto al tamaño real de las letras de las fonts:

 class TextWrapper(object): """ Helper class to wrap text in lines, based on given text, font and max allowed line width. """ def __init__(self, text, font, max_width): self.text = text self.text_lines = [ ' '.join([w.strip() for w in l.split(' ') if w]) for l in text.split('\n') if l ] self.font = font self.max_width = max_width self.draw = ImageDraw.Draw( Image.new( mode='RGB', size=(100, 100) ) ) self.space_width = self.draw.textsize( text=' ', font=self.font )[0] def get_text_width(self, text): return self.draw.textsize( text=text, font=self.font )[0] def wrapped_text(self): wrapped_lines = [] buf = [] buf_width = 0 for line in self.text_lines: for word in line.split(' '): word_width = self.get_text_width(word) expected_width = word_width if not buf else \ buf_width + self.space_width + word_width if expected_width <= self.max_width: # word fits in line buf_width = expected_width buf.append(word) else: # word doesn't fit in line wrapped_lines.append(' '.join(buf)) buf = [word] buf_width = word_width if buf: wrapped_lines.append(' '.join(buf)) buf = [] buf_width = 0 return '\n'.join(wrapped_lines) 

Ejemplo de uso:

 wrapper = TextWrapper(text, image_font_intance, 800) wrapped_text = wrapper.wrapped_text() 

Probablemente no sea súper rápido, porque representa el texto completo palabra por palabra, para determinar el ancho de las palabras. Pero para la mayoría de los casos debería estar bien.

 text = textwrap.fill("test ",width=35) self.draw.text((x, y), text, font=font, fill="Black") 

Podría usar PIL.ImageDraw.Draw.multiline_text() .

 draw.multiline_text((WIDTH, HEIGHT), TEXT, fill=FOREGROUND, font=font) 

Incluso se ajusta el spacing o se align utilizando los mismos nombres de parámetros.