web-dev-qa-db-fra.com

python PIL dessiner un texte multiligne sur l'image

J'essaie d'ajouter du texte au bas de l'image et je l'ai fait, mais si mon texte est plus long que la largeur de l'image, il est coupé des deux côtés. Pour simplifier, j'aimerais que le texte soit composé de plusieurs lignes s'il est plus long 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')
29
user985541

Vous pouvez utiliser textwrap.wrap pour séparer text en une liste de chaînes de caractères contenant 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
40
unutbu

La réponse acceptée englobe le texte sans mesurer la police (40 caractères maximum, quelles que soient 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 remplir ou sous-remplir la zone. 

Voici une bibliothèque simple qui résout le problème correctement: https://Gist.github.com/turicas/1455973

9
pryma

Toutes les recommandations sur l'utilisation de textwrap ne permettent pas de déterminer la largeur correcte des polices non monospaces (comme Arial, utilisée dans l'exemple de code de la rubrique).

J'ai écrit une classe d'assistance simple pour envelopper le texte concernant le redimensionnement des lettres de police réelles:

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 très rapide, car il rend le texte entier Word par Word, afin de déterminer la largeur des mots. Mais dans la plupart des cas, ça devrait aller.

1
Igor Pomaranskiy