虽然仅仅为了检查字符串长度而添加一个新的第三方依赖库(如PIL)可能不是一个好主意,但是可以使用一个第三方库来生成可重用的近似函数。这样,解决方案既快速又无需依赖,同时也能保证准确性。
如果所涉及的字体是TrueType字体,您可以使用
PIL.ImageFont.FreeTypeFont.getbbox。提示:最好先使用一个过大的字体计算宽度,因为getbbox返回整数。
使用自定义字体Inter-Regular的示例:
第一部分:生成宽度
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
第二部分:创建宽度计算器函数
现在只需将您的函数定义为:
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](https://istack.dev59.com/9fUp9.webp)
由于从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)