根据字符宽度将字符串分成多行(python)

时间:2017-05-07 04:15:56

标签: python textwrapping python-textprocessing

我通过PIL在基本图像上绘制文字。如果所有字符的组合宽度超过基本图像的宽度,则其中一个要求是溢出到下一行。

目前,我正在使用textwrap.wrap(text, width=16)来完成此任务。这里width定义了一行中容纳的字符数。现在,文本可以是任何内容,因为它是用户生成的。所以问题在于,由于字体类型,字体大小和字符选择,硬编码width不会考虑width的可变性。

我的意思是什么?

好吧想象一下,我使用DejaVuSans.ttf,大小为14. W的长度为14,而“{1}}的长度为14。{i}对于宽度为400的基本图像,一行中最多可容纳100个i个字符。但只有29 W个字符。我需要制定一种更智能的包装方式到下一行,当字符宽度的总和超过基本图像宽度时,字符串被打破。

有人可以帮我制定这个吗?一个说明性的例子会很棒!

3 个答案:

答案 0 :(得分:1)

由于你知道每个字符的宽度,你应该把它变成一个字典,从中得到宽度来计算字符串宽度:

char_widths = {
    'a': 9,
    'b': 11,
    'c': 13,
    # ...and so on
}

从这里您可以查找每个字母并使用该金额来检查您的宽度:

current_width = sum([char_widths[letter] for letter in word])

答案 1 :(得分:1)

如果精度对您很重要,获得实际文本宽度的最佳方法是实际呈现它,因为字体度量并不总是线性的,关于字距调整或字体大小(参见here),例如,因此不容易预测。 我们可以使用内部使用核心字体呈现方法的ImageFont方法get_size来接近最佳断点(参见PIL github

def break_text(txt, font, max_width):

    # We share the subset to remember the last finest guess over 
    # the text breakpoint and make it faster
    subset = len(txt)
    letter_size = None

    text_size = len(txt)
    while text_size > 0:

        # Let's find the appropriate subset size
        while True:
            width, height = font.getsize(txt[:subset])
            letter_size = width / subset

            # min/max(..., subset +/- 1) are to avoid looping infinitely over a wrong value
            if width < max_width - letter_size and text_size >= subset: # Too short
                subset = max(int(max_width * subset / width), subset + 1)
            elif width > max_width: # Too large
                subset = min(int(max_width * subset / width), subset - 1)
            else: # Subset fits, we exit
                break

        yield txt[:subset]
        txt = txt[subset:]   
        text_size = len(txt)

并像这样使用它:

from PIL import Image
from PIL import ImageFont
img = Image.new('RGBA', (100, 100), (255,255,255,0))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype("Helvetica", 12)
text = "This is a sample text to break because it is too long for the image"

for i, line in enumerate(break_text(text, font, 100)):
    draw.text((0, 16*i), line, (255,255,255), font=font)

答案 2 :(得分:0)

最简单的解决方案可能只是使用等宽字体,其中每个字符的宽度相同。显然你不能总是使用一个,但是当你可以使用时它会简单得多。