Python PIL dessine du texte multiligne sur l'image
j'essaie d'ajouter du texte au bas de l'image et en fait je l'ai fait, mais dans le cas de mon texte est plus long que la largeur de l'image il est coupé des deux côtés, pour simplifier je voudrais que le texte soit en plusieurs lignes si elle est plus longue que la largeur de l'image. Voici mon code:
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 = (W-w)/2, (H-h)/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')
5 réponses
Vous pouvez utiliser textwrap.wrap
pause text
dans une liste de chaînes, chacune au plus width
caractères:
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 réponse acceptée enveloppe le texte sans mesurer la police (max 40 caractères, peu importe la taille de la police et la largeur de la boîte), de sorte que les résultats ne sont qu'approximatifs et peuvent facilement déborder ou sous-remplir la boîte.
Voici une bibliothèque simple qui résout le problème correctement: https://gist.github.com/turicas/1455973
text = textwrap.fill("test ",width=35)
self.draw.text((x, y), text, font=font, fill="Black")
Toutes les recommandations concernant textwrap
l'utilisation ne permet pas de déterminer la largeur correcte pour les polices non-monospacées (comme Arial, utilisé dans le code d'exemple de sujet).
j'ai écrit la classe helper simple pour envelopper le texte concernant les vraies lettres de police de taille:
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)
Exemple d'utilisation:
wrapper = TextWrapper(text, image_font_intance, 800)
wrapped_text = wrapper.wrapped_text()
ce n'est probablement pas super rapide, car il rend le texte entier mot par mot, pour déterminer la largeur des mots. Mais pour la plupart des cas, cela devrait être OK.
Vous pouvez utiliser PIL.ImageDraw.Draw.multiline_text()
.
draw.multiline_text((WIDTH, HEIGHT), TEXT, fill=FOREGROUND, font=font)
même spacing
ou align
en utilisant les mêmes noms param.