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

问题描述:

我正在通过PIL在基本图像上绘制文本。如果所有字符的合并宽度超过基础图像的宽度,则其中一个要求是溢出到下一行。根据字符宽度将字符串拆分为多行(python)

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

我的意思是?

想象一下,我使用的是DejaVuSans.ttf,大小为14. W的长度为14,而'i'为4.对于宽度为400的基本图像,最多为100 i字符可以放在一行中。但只有29 W个字符。我需要制定一个更明智的方式来包装到下一行,当字符宽度总和超过基本图像宽度时,字符串被破坏。

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

你既然知道每个字符的宽度,你应该做的是为一本字典,从中获取的宽度来计算stringwidth:

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

从这里你可以查找每个字母,并使用该总和检查你的宽度:

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

或如何使用'font.getsize( c)[0]'在飞行中,为每个字符'c'? –

+1

如果你这样做的话,这可能会奏效:'current_width = sum([font.getsize(c)[0] for c in word])' - 如果你可以创建一个字典,访问速度比做这个函数调用要快每一次。 – JacobIRR

+0

感谢您的提示。 –

如果您精确的问题,才能获得真正的文本的宽度最好的方法是实际渲染它,因为字体规格并不总是线性的,关于字距或字体大小(见例如,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) 
+0

谢谢。我实际上也使用'getsize'来解决问题 - 下面看看我的代码如何最终变成如下形式:http://*.com/q/43828154/4936905 –