如何在 Pillow 中的单词(不是字符)之间换行?

How to break to new line between words (not characters) in Pillow?

我正在尝试使用 Python 中的 PIL 生成一张海报。想法是海报的宽度是固定的,但是根据内容动态设置高度。这是相关代码:

from PIL import Image, ImageDraw, ImageFont


# From 

def break_fix(text, width, font, draw):
    """
    Fix line breaks in text.
    """
    if not text:
        return
    lo = 0
    hi = len(text)
    while lo < hi:
        mid = (lo + hi + 1) // 2
        t = text[:mid]
        w, h = draw.textsize(t, font=font)
        if w <= width:
            lo = mid
        else:
            hi = mid - 1
    t = text[:lo]
    w, h = draw.textsize(t, font=font)
    yield t, w, h
    yield from break_fix(text[lo:], width, font, draw)


# Edited from 

def fit_text(img, text, color, font, x_start_offset=0, x_end_offset=0, center=False):
    """
    Fit text into container after applying line breaks. Returns the total 
    height taken up by the text, which can be used to create containers of 
    dynamic heights.
    """
    width = img.size[0] - x_start_offset - x_end_offset
    draw = ImageDraw.Draw(img)
    pieces = list(break_fix(text, width, font, draw))
    height = sum(p[2] for p in pieces)
    y = (img.size[1] - height) // 2
    h_taken_by_text = 0
    for t, w, h in pieces:
        if center:
            x = (img.size[0] - w) // 2
        else:
            x = x_start_offset
        draw.text((x, y), t, font=font, fill=color)
        new_width, new_height = draw.textsize(t, font=font)
        y += h
        h_taken_by_text += new_height
    return h_taken_by_text


def generate_text_section(width, text, color, font, x_start_offset, x_end_offset, v_spacing):
    """
    Generates an image for a text section.
    """
    # Calculate height using "fake" canvas
    img = Image.new('RGB', (width, 1), color='white')
    calc_height = fit_text(
        img, text.upper(), color, font, x_start_offset, x_end_offset, False
    )

    # Create real canvas and fit text
    img = Image.new('RGB', (width, calc_height + v_spacing), color='white')
    fit_text(
        img, text.upper(), color, font, x_start_offset, x_end_offset, False
    )

    return img


test_img_1 = generate_text_section(
    width=400,
    text='But I must explain to you',
    color='black',
    font=ImageFont.truetype('fonts/RobotoMono-Bold.ttf', 14),
    x_start_offset=30,
    x_end_offset=30,
    v_spacing=30
)
test_img_1.show()
test_img_1.save('1.png')


test_img_2 = generate_text_section(
    width=400,
    text='But I must explain to you how all this mistaken idea of denouncing',
    color='black',
    font=ImageFont.truetype('fonts/RobotoMono-Bold.ttf', 14),
    x_start_offset=30,
    x_end_offset=30,
    v_spacing=30
)
test_img_2.show()
test_img_2.save('2.png')

它是如何工作的,我首先使用 fit_text() 方法计算所需的高度(在打破溢出的文本之后),然后我使用该高度创建一个 canvas 可以容纳文本。这工作得很好。这是使用上面的代码生成的两个图像:

这真的很棒,但是,溢出是在字符之间完成的,所以在第二张图片上,单词 MISTAKEN 被打破了。理想情况下,我想在 之间打断 个单词,或者至少在换行符前添加一个“-”。

谁能帮我解决这个问题?完全披露,这两种方法都是从这个 .

编辑的

我觉得最好是修改break_fix():

def break_fix(text, width, font, draw):
    """
    Fix line breaks in text.
    """
    if not text:
        return
    if isinstance(text, str):
        text = text.split() # this creates a list of words

    lo = 0
    hi = len(text)
    while lo < hi:
        mid = (lo + hi + 1) // 2
        t = ' '.join(text[:mid]) # this makes a string again
        w, h = draw.textsize(t, font=font)
        if w <= width:
            lo = mid
        else:
            hi = mid - 1
    t = ' '.join(text[:lo]) # this makes a string again
    w, h = draw.textsize(t, font=font)
    yield t, w, h
    yield from break_fix(text[lo:], width, font, draw)

第三个盲文示例:

test_img_3 = generate_text_section(
    width=400,
    text='''Lorem ipsum dolor sit amte, consectutuer adipiscing elit.
Ut purus elit vestibulumut, placerat, adpiscing vitae, felis. Curabutiur dictm gravida mauris.
Nam Arcu libero, nonummy eget, consectetuer id, vulputate a, magna. Donec vehicula auge eu newue.
Pellentesque habitant morbi tristique senectus es netus et malesuada fames ac turpis egestas. Mauris ut leo.
''',
    color='black',
    font=ImageFont.truetype('/home/jovyan/shared/fonts/SourceSansPro.ttf', 14),
    x_start_offset=30,
    x_end_offset=30,
    v_spacing=30
)
test_img_3.show()
test_img_3.save('2.png')

输出

评论

我没有你的字体,因此你的输出可能会改变。