【问题标题】:Breaking string into multiple lines according to character width (python)根据字符宽度将字符串分成多行(python)
【发布时间】:2017-10-05 06:46:03
【问题描述】:

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

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

我是什么意思?

假设我正在使用DejaVuSans.ttf,大小为 14。W 的长度为 14,而“i”为 4。对于宽度为 400 的基本图像,最多可以有 100 个i 字符容纳在一条线上。但只有 29 个W 个字符。我需要制定一种更智能的方式来换行到下一行,当字符宽度的总和超过基本图像宽度时,字符串会被破坏。

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

【问题讨论】:

    标签: python word-wrap text-processing


    【解决方案1】:

    既然你知道每个字符的宽度,你应该把它写成一个字典,从中你可以得到宽度来计算字符串宽度:

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

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

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

    【讨论】:

    • 或者对于每个字符c 即时使用font.getsize(c)[0] 怎么样?
    • 如果你这样做可能会起作用:current_width = sum([font.getsize(c)[0] for c in word]) - 如果你可以制作字典,那么访问会比每次调用这个函数更快。
    • 感谢您的提示。
    【解决方案2】:

    如果精度对您很重要,那么获得真实文本宽度的最佳方法是实际呈现它,因为字体度量并不总是线性的,例如关于字距调整或字体大小(参见 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)
    

    【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2017-07-05
      • 1970-01-01
      • 1970-01-01
      • 2012-08-17
      • 1970-01-01
      • 2014-12-14
      • 1970-01-01
      • 1970-01-01
      • 2022-11-18
      相关资源
      最近更新 更多