Python 中文本字符串的粗略宽度?

问题描述 投票:0回答:4

如何使用 Python 来近似给定文本字符串的字体宽度?

我正在寻找一个原型类似于以下的函数:

def getApproximateFontWidth(the_string, font_name="Arial", font_size=12):
   return ... picas or pixels or something similar ...

我并不是在寻找任何非常严格的东西,一个近似值就可以了。

这样做的动机是我在 web 应用程序的后端生成一个截断的字符串并将其发送到前端进行显示。大多数情况下,字符串都是小写,但有时字符串全部大写,使得它们非常宽。如果字符串没有被正确截断,它看起来很难看。我想知道根据字符串的大致宽度截断多少字符串。如果下降 10% 也没什么大不了的,这是一个装饰功能。

python fonts
4个回答
14
投票

下面是我的简单解决方案,它的准确率约为 80%,非常适合我的目的。它仅适用于 Arial,仅适用于 ascii,并且假定 12 pt 字体,但它也可能与其他字体成比例。

import string

def getApproximateArialStringWidth(st):
    size = 0 # in milinches
    for s in st:
        if s in 'lij|\' ': size += 37
        elif s in '![]fI.,:;/\\t': size += 50
        elif s in '`-(){}r"': size += 60
        elif s in '*^zcsJkvxy': size += 85
        elif s in 'aebdhnopqug#$L+<>=?_~FZT' + string.digits: size += 95
        elif s in 'BSPEAKVXY&UwNRCHD': size += 112
        elif s in 'QGOMm%W@': size += 135
        else: size += 50
    return size * 6 / 1000.0 # Convert to picas

如果你想截断一个字符串,这里是:

import string

def truncateToApproximateArialWidth(st, width):
    size = 0 # 1000 = 1 inch
    width = width * 1000 / 6 # Convert from picas to miliinches
    for i, s in enumerate(st):
        if s in 'lij|\' ': size += 37
        elif s in '![]fI.,:;/\\t': size += 50
        elif s in '`-(){}r"': size += 60
        elif s in '*^zcsJkvxy': size += 85
        elif s in 'aebdhnopqug#$L+<>=?_~FZT' + string.digits: size += 95
        elif s in 'BSPEAKVXY&UwNRCHD': size += 112
        elif s in 'QGOMm%W@': size += 135
        else: size += 50
        if size >= width:
            return st[:i+1]
    return st

然后是以下内容:

>> width = 15
>> print truncateToApproxArialWidth("the quick brown fox jumps over the lazy dog", width) 
the quick brown fox jumps over the
>> print truncateToApproxArialWidth("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG", width) 
THE QUICK BROWN FOX JUMPS

渲染时,这些字符串的宽度大致相同:

敏捷的棕色狐狸跳过

敏捷的棕色狐狸跳跃


4
投票

您可以使用 PIL 渲染带有文本的图像,然后确定生成的图像宽度。

http://effbot.org/imagingbook/imagefont.htm


1
投票

我使用了一个库来执行此操作,但它需要 pygame: http://inside.catlin.edu/site/compsci/ics/python/graphics.py 看下sizeString


1
投票

虽然添加新的第 3 方依赖项(例如 PIL)仅用于检查字符串长度(如果可能不是一个好主意),但可以使用第 3 方库仅用于生成可重用的近似函数。这样,解决方案快速、无依赖性且准确。

如果相关字体是 TrueType 字体,您可以使用 PIL.ImageFont.FreeTypeFont.getbbox提示:最好先计算过大字体的宽度,因为 getbbox 返回整数。

自定义字体示例:Inter-Regular

第 1 部分:生成宽度

import string

from PIL import ImageFont

WIDTH_DICT = dict()

supported_chars = [c for c in string.printable if not c.isspace() or c == ' ']


font_file_path = str(Path("~/.fonts/Inter-Regular.ttf").expanduser())
font = ImageFont.truetype(font_file_path, 15)

for char in supported_chars:
    left, _, right, _ = font.getbbox(char)
    width = right - left
    WIDTH_DICT[char] = width
    

AVERAGE_WIDTH = sum(WIDTH_DICT.values()) / len(WIDTH_DICT)

print(f'{WIDTH_DICT=}')
print(f'{AVERAGE_WIDTH=}')

输出

WIDTH_DICT={'0': 9, '1': 7, '2': 9, '3': 10, '4': 10, '5': 9, '6': 9, '7': 9, '8': 9, '9': 9, 'a': 8, 'b': 9, 'c': 8, 'd': 9, 'e': 9, 'f': 6, 'g': 9, 'h': 9, 'i': 4, 'j': 5, 'k': 8, 'l': 4, 'm': 13, 'n': 9, 'o': 9, 'p': 9, 'q': 9, 'r': 6, 's': 8, 't': 5, 'u': 9, 'v': 8, 'w': 12, 'x': 8, 'y': 8, 'z': 8, 'A': 10, 'B': 10, 'C': 11, 'D': 11, 'E': 9, 'F': 9, 'G': 11, 'H': 11, 'I': 4, 'J': 8, 'K': 10, 'L': 8, 'M': 13, 'N': 11, 'O': 11, 'P': 10, 'Q': 11, 'R': 10, 'S': 10, 'T': 10, 'U': 11, 'V': 10, 'W': 14, 'X': 10, 'Y': 10, 'Z': 9, '!': 4, '"': 6, '#': 10, '$': 10, '%': 12, '&': 10, "'": 3, '(': 5, ')': 5, '*': 8, '+': 10, ',': 4, '-': 7, '.': 4, '/': 5, ':': 4, ';': 4, '<': 10, '=': 10, '>': 10, '?': 8, '@': 14, '[': 5, '\\': 5, ']': 5, '^': 7, '_': 8, '`': 7, '{': 5, '|': 5, '}': 5, '~': 10, ' ': 4}
AVERAGE_WIDTH=8.31578947368421

第 2 部分:创建宽度计算器功能

现在只需将您的函数定义为:


WIDTH_DICT={'0': 9, '1': 7, '2': 9, '3': 10, '4': 10, '5': 9, '6': 9, '7': 9, '8': 9, '9': 9, 'a': 8, 'b': 9, 'c': 8, 'd': 9, 'e': 9, 'f': 6, 'g': 9, 'h': 9, 'i': 4, 'j': 5, 'k': 8, 'l': 4, 'm': 13, 'n': 9, 'o': 9, 'p': 9, 'q': 9, 'r': 6, 's': 8, 't': 5, 'u': 9, 'v': 8, 'w': 12, 'x': 8, 'y': 8, 'z': 8, 'A': 10, 'B': 10, 'C': 11, 'D': 11, 'E': 9, 'F': 9, 'G': 11, 'H': 11, 'I': 4, 'J': 8, 'K': 10, 'L': 8, 'M': 13, 'N': 11, 'O': 11, 'P': 10, 'Q': 11, 'R': 10, 'S': 10, 'T': 10, 'U': 11, 'V': 10, 'W': 14, 'X': 10, 'Y': 10, 'Z': 9, '!': 4, '"': 6, '#': 10, '$': 10, '%': 12, '&': 10, "'": 3, '(': 5, ')': 5, '*': 8, '+': 10, ',': 4, '-': 7, '.': 4, '/': 5, ':': 4, ';': 4, '<': 10, '=': 10, '>': 10, '?': 8, '@': 14, '[': 5, '\\': 5, ']': 5, '^': 7, '_': 8, '`': 7, '{': 5, '|': 5, '}': 5, '~': 10, ' ': 4}
AVERAGE_WIDTH=8.31578947368421

def get_width_inter_font_15(string:str) -> float:
  return sum(WIDTH_DICT.get(s, AVERAGE_WIDTH) for s in string)

这可以用作:

>>> get_width_inter_font_15("the quick brown fox jumps over the")
252
>>> get_width_inter_font_15("THE QUICK BROWN FOX JUMPS")
231

这个比率 252/231 是 1.091,这与我在比较从字符串创建的图像宽度时看到的非常接近:1.078(由 Inkscape 给出)

enter image description here

由于 getbbox 返回的宽度是整数,因此使用较大的字体会有所帮助。例如,字体大小为 500:

>>> get_width_inter_font_500("THE QUICK BROWN FOX JUMPS")
7722
>>> get_width_inter_font_500("the quick brown fox jumps over the")
8358

这里估计的比率是 1.082,仅比 Inkscape 所说的大 0.4%。

关于速度

生成的函数

get_width_inter_font_500
比使用PIL快约1000倍:

>>> %timeit get_width_inter_font_500("the quick brown fox jumps over the")
3.04 µs ± 10.9 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
>>> def pil_width(string):
      total = 0
      for char in string:
          left, _, right, _ = font.getbbox(char)
          width = right - left
          total += width
      return total
    
>>> pil_width("the quick brown fox jumps over the")
8358
>>> %timeit pil_width("the quick brown fox jumps over the")
3.6 ms ± 22.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
© www.soinside.com 2019 - 2024. All rights reserved.